조인트 스파이더 크롤러 데이터, 온라인 접속 기반의 웹사이드 데이터 시각화 플랫폼
데이터 소스는 Joint-spider 프로젝트에서 나옵니다.
백엔드: django-restframework
프런트엔드: Vue
# 环境配置需要 python3.7 redis mysql vue vue-cli3.x
#
# 项目启动
# 1. 后端服务启动,进入项目HouseAnalysis
python manage.py runserver
# 2. 前端项目启动,进入项目目录HouseAnalysisWeb
npm run dev
# 3. 前端项目打包
npm run build-prod
# 4. 访问地址:http://localhost:9527/
# 5. 后端接口地址: http://localhost:8000/
커뮤니티 주택 목록 세부정보
검색 속성 페이지
특정 부동산 세부정보 페이지
집 주변
# 项目依赖
Django==2.1.5
django-cors-headers==2.4.0
django-crispy-forms==1.7.2
django-filter==2.0.0
django-formtools==2.1
django-guardian==1.4.9
django-import-export==1.1.0
django-redis==4.10.0
django-reversion==3.0.2
djangorestframework==3.9.0
djangorestframework-jwt==1.11.0
drf-extensions==0.4.0
PyMySQL==0.9.3
redis==3.0.1
SQLAlchemy==1.3.2
기본 프로세스
장고 라이프사이클:
프런트 엔드는 요청을 보냅니다-->Django의 wsgi-->미들웨어-->라우팅 시스템->보기-->ORM 데이터베이스 작업-->템플릿-->사용자에게 데이터 반환
나머지 프레임워크 수명 주기:
프론트엔드는 요청을 보냅니다-->Django의 wsgi-->Middleware-->Routing system_Execute CBV의 as_view(), 이는 내부 dispath 메소드를 실행합니다-->dispath를 실행하기 전에 버전 분석 및 렌더러가 있습니다--> dispath, 요청 캡슐화-->버전-->인증-->권한-->현재 제한-->보기-->뷰가 캐싱을 사용하는 경우(request.data 또는 request.query_params )는 파서를 사용합니다 --> 뷰는 데이터를 처리하고 직렬화(데이터 직렬화 또는 확인)를 사용합니다 --> 뷰는 페이징을 사용하여 데이터를 반환합니다.
프로젝트 파일 트리:
# 1. 初始化项目
django-admin startproject HouseAnalysis
# 2. 创建相关应用
cd HouseAnalysis
django-admin startapp house
# 项目根目录下创建extra_app 文件夹,存放xadmin压缩文件,运行
pip3 install xadmin
# 1. 配置settings.py 文件 位于\HouseAnalysisHouseAnalysissettings.py
# 找到INSTALLED_APPS,加入配置项
' corsheaders ' ,
' xadmin ' ,
' crispy_forms ' ,
' rest_framework ' ,
' django_filters ' ,
' house '
# 上述配置向是用于将app注册到项目中
# 2. 增加跨域支持
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = (
' * '
)
CORS_ALLOW_METHODS = (
' DELETE ' ,
' GET ' ,
' OPTIONS ' ,
' PATCH ' ,
' POST ' ,
' PUT ' ,
' VIEW ' ,
)
CORS_ALLOW_HEADERS = (
' XMLHttpRequest ' ,
' X_FILENAME ' ,
' accept-encoding ' ,
' authorization ' ,
' content-type ' ,
' dnt ' ,
' origin ' ,
' user-agent ' ,
' x-csrftoken ' ,
' x-requested-with ' ,
' Pragma ' ,
)
# 3. 在DATABASES中配置MySQL数据库信息
DATABASES = {
' default ' : {
' ENGINE ' : ' django.db.backends.mysql ' ,
' NAME ' : ' house ' ,
' USER ' : ' root ' ,
' PASSWORD ' : ' 123456 ' ,
' HOST ' : ' 127.0.0.1 ' ,
' PORT ' : ' 3306 ' ,
' OPTIONS ' :{
' init_command ' : " SET sql_mode='STRICT_TRANS_TABLES' "
},
}
}
# 4. 配置drf缓存
REST_FRAMEWORK_EXTENSIONS = {
# 10s后失效
' DEFAULT_CACHE_RESPONSE_TIMEOUT ' : 10
}
# 5. 配置redis缓存
CACHES = {
" default " : {
" BACKEND " : " django_redis.cache.RedisCache " ,
" LOCATION " : " redis://127.0.0.1:6379/3 " ,
" OPTIONS " : {
" CLIENT_CLASS " : " django_redis.client.DefaultClient " ,
}
}
}
참고: 이 프로젝트는 인터페이스 개발이며 템플릿 템플릿과 정적 리소스를 포함하지 않으므로 이 두 항목을 구성할 필요가 없습니다.
HouseAnalyticHouseAnalyticurls.py를 입력하고 라우팅 정보를 구성합니다.
urlpatterns
목록에 path('xadmin/', xadmin.site.urls)
, re_path(r'docs/', include_docs_urls(title="HA"))
추가합니다. xadmin은 관리 페이지 라우팅입니다.
Docs는 아래와 같이 http://localhost:8000/docs/를 통해 액세스할 수 있는 인터페이스 문서 라우팅입니다.
Django 모델 디자인은 Django의 5가지 기본 핵심 디자인(모델 디자인, URL 구성, 뷰 작성, 템플릿 디자인, From use) 중 하나이며, MVC(MVT) 모델에서도 중요한 링크입니다.
모델은 데이터베이스가 아닌 데이터 모델입니다. 데이터의 구조와 데이터 간의 논리적 관계를 설명합니다. Django에서 모델 디자인은 본질적으로 클래스입니다. 각 모델은 데이터베이스 테이블에 매핑됩니다.
HouseAnalytichousemodels.py를 입력하고 Api,Elevator,Floor,Layout,Region,Decortion,Purposes,Orientation,Constructure,Community,CommunityRange
모델을 추가하여 직렬화용 데이터를 제공합니다.
모델이 생성된 후 데이터 마이그레이션을 수행합니다.
# 进入项目根目录
python manage.py makemigrations
# 同步至数据库
python manage.py migrate
데이터를 직렬화하려면 house 폴더에 serializer.py 파일을 새로 생성하세요.
일반적으로 모델 클래스의 필드를 포함하고 자체 필드 유형 규칙을 갖는 직렬 변환기(기본적으로 클래스)를 정의합니다. Serializer를 구현한 후 직렬화 작업을 위한 직렬화된 객체 및 쿼리 세트를 생성하고 직렬화된 object.data를 통해 데이터를 얻을 수 있습니다. (직접 사전을 구성한 후 Json 데이터를 반환할 필요가 없습니다.)
직렬화에 사용되는 경우 모델 클래스 객체를 인스턴스 매개변수에 전달합니다.
역직렬화에 사용되는 경우 역직렬화할 데이터를 data 매개변수에 전달합니다.
인스턴스 및 데이터 매개변수 외에도 Serializer 개체를 생성할 때 context 매개변수를 통해 추가 데이터를 추가할 수도 있습니다.
여기서는 일반 Serializer와 동일하지만 다음을 제공하는 모델 serializer(ModelSerializer)를 사용합니다.
예: 간단한 코드를 사용하여 집 콘텐츠를 직렬화할 수 있습니다.
class HouseSerializer ( serializers . ModelSerializer ):
class Meta :
# 需要序列化的model
model = Api
# 需要序列化的字段
fields = "__all__"
같은 방식으로 ElevatorSerializer,FloorSerializer,LayoutSerializer,RegionSerializer,RegionS,CommunitySerializer,CommunityRangeSerializer,DecortionSerializer,PurposesSerializer,ConstructureSerializer,OrientationSerializer
를 포함한 나머지 모델을 직렬화합니다. 그중 RegionS
직렬 변환기는 CommunitySerializer
및 CommunityRangeSerializer
의 우수한 직렬 변환기 역할을 합니다.
뷰의 기능: 직설적으로 말하면 프런트 엔드 요청을 수신하고 데이터 처리를 수행하는 것입니다.
(여기서의 처리에는 다음이 포함됩니다. 프런트 엔드가 GET 요청인 경우 쿼리 세트를 구성하고 결과를 반환합니다. 이 프로세스는 직렬화 입니다. 프런트 엔드가 POST 요청인 경우 데이터베이스를 변경하려는 경우 프런트 엔드에서 보낸 데이터를 가져와야 합니다. 데이터를 확인하고 데이터베이스에 써야 합니다. 이 프로세스를 역직렬화 라고 합니다.
가장 독창적인 뷰는 이러한 논리적 처리를 구현할 수 있지만 요청이 다른 경우 클래스 뷰에 여러 메서드를 정의하여 자체 처리를 구현해야 문제를 해결할 수 있지만 결함이 있습니다. 각 함수의 논리는 비슷합니다. 요청 읽기, 데이터베이스에서 데이터 가져오기, 데이터베이스에 내용 쓰기 및 결과를 프런트 엔드에 반환합니다. 이로 인해 중복된 코드가 많이 생성됩니다.
REST API 개발 뷰에서는 각 뷰의 구체적인 데이터 작업이 다르지만 추가, 삭제, 수정 및 확인의 구현 프로세스가 기본적으로 일상적이므로 코드의 이 부분을 재사용하고 단순화하여 작성할 수도 있습니다.
추가 : 요청 데이터 확인 -> 역직렬화 프로세스 실행 -> 데이터베이스 저장 -> 저장된 객체 직렬화 및 반환
삭제 : 삭제할 데이터가 존재하는지 확인 -> 데이터베이스 삭제 수행
수정 : 수정할 데이터 존재 여부 판단 -> 요청한 데이터 확인 -> 역직렬화 과정 수행 -> 데이터베이스 저장 -> 저장된 객체를 직렬화 및 반환
쿼리 : 데이터베이스 쿼리 -> 데이터 직렬화 및 반환
뷰는 views.py 파일에 작성되며 여기서는 개발을 위해 클래스 인식 다이어그램을 사용합니다.
http://127.0.0.1/api/v1/allhouse
인 경우 모든 주택 정보의 직렬화된 결과를 반환하고 노출해야 합니다.http://127.0.0.1/api/v1/allhouse
/1인 경우 집 ID(다음 위치의 ID)를 변경해야 합니다. 이번에는 요구사항 매개변수를 지정하는 직렬화 결과가 1이 반환되어 노출되도록 구성할 수 있습니다.{id:xxx,values:xxx...}
인 경우 현재 정보를 확인하고 현재 데이터를 추가해야 합니다. 합법적인 경우 데이터베이스.예:
# CacheResponseMixin: 缓存,内存级别
# ListModelMixin处理GET(all)请求
# RetrieveModelMixin处理(id)请求
# 基础类GenericViewSet,提供分页,过滤,排序等功能
class HouseListViewSet ( CacheResponseMixin , mixins . ListModelMixin , mixins . RetrieveModelMixin , GenericViewSet ):
queryset = Api . objects . all ()
# 房源序列化
serializer_class = HouseSerializer
# 房源分页
pagination_class = HousePagination
# 自定义排序,过滤类
filter_backends = ( DjangoFilterBackend , filters . SearchFilter , filters . OrderingFilter )
# 过滤信息字段
filter_fields = ( 'price' ,)
# 搜搜字段
search_fields = ( 'title' , 'region' , 'community_name' )
# 排序字段
ordering_fields = ( 'price' , 'unit_price' , 'construction_area' )
# 重写retrieve方法,实现获取单个序列化对象信息
def retrieve ( self , request , * args , ** kwargs ):
instance = self . get_object ()
serializer = self . get_serializer ( instance )
return Response ( serializer . data )
# 分页配置项
class HousePagination ( PageNumberPagination ):
page_size = 30
page_size_query_param = 'page_size'
page_query_param = "page"
max_page_size = 100
ElevaorViewSet,FloorViewSet,LayoutViewSet,RegionViewSet,CommunityViewSet,CommunityPagination,AllCommunityViewSet,CommunityRangeViewSet,DecortionViewSet,PurposesViewSet,ConstructureViewSet,OrientationViewSet
를 포함한 다른 보기도 유사합니다.
CommunityViewSet
클래스의 경우 특정 Community_id를 기반으로 모든 정보를 반환해야 합니다. 현재로서는 RetrieveModelMixin
클래스의 retrieve
메서드를 사용할 수 없으며 특정 모델을 반환하려면 get_queryset
메서드를 재정의해야 합니다.
def get_queryset ( self ):
if self . request . GET [ 'rid' ] == '0' :
# 获取所有
return Community . objects . all ()
else :
return Community . objects . filter ( region = self . request . GET [ 'rid' ])
기타 매개변수
경로 등록을 위해 rest_framework
의 DefaultRouter
사용하십시오.
from rest_framework . routers import DefaultRouter
router = DefaultRouter ()
router . register ( r'v1/api/all_house' , HouseListViewSet , base_name = "house" )
......
urlpatterns에 라우터를 추가합니다:
path ( '' , include ( router . urls )),
이때 요청이 기존 경로에 접근하면 해당 경로는 요청을 뷰 클래스로 전달한 후, 백엔드에서 노출하는 인터페이스 데이터인 직렬화된 결과를 반환합니다.
[외부 링크 이미지 전송에 실패했습니다. 소스 사이트에 안티 리칭 메커니즘이 있을 수 있습니다. 이미지를 저장하고 직접 업로드하는 것이 좋습니다. (img-JmH9EULX-1588257851195) (imgs/interface data.png)]
데이터 설명
HTTP 상태 코드:
# 将所有房源数,小区数,总体单价价均价,总体总价均价,存入redis
def insertIndexInfo ():
all_number = house . count ()
com_number = Api . objects . values ( 'community_name' ). distinct (). count ()
mean_price = format ( house . aggregate ( Avg ( 'price' ))[ 'price__avg' ], '.3f' )
mean_unit_price = format ( house . aggregate ( Avg ( 'unit_price' ))[ 'unit_price__avg' ], '.3f' )
data = {
"all_number" : all_number ,
"com_number" : com_number ,
"mean_price" : mean_price ,
"mean_unit_price" : mean_unit_price
}
redis_conn . mset ( data )
# 1. 定义专门数据库操作类InsertIntoMysql
class InsertIntoMysql ():
# 属性中定义sql语句获取数据
def __init__ ( self ):
self . elevator_sql = 'select unit_price, price, elevator from house_api'
# ......
# 定义数据插入方法
def insertElevator ( self ):
index , values , price_mean , unit_price_mean = multiple ( self . elevator_sql , engine , 'elevator' )
for i in range ( len ( index )):
elevator = Elevator ()
elevator . version = 'v1'
elevator . title = '电梯分布情况'
elevator . has_ele = index [ i ]
elevator . el_num = values [ i ]
elevator . mean_price = price_mean [ i ]
elevator . mean_unit_price = unit_price_mean [ i ]
elevator . save ()
# 其他剩余数据插入方法
# ......
# 2. 数据处理模块,包括单因子和对因子处理
# 利用数据处理模块pandas
def single ( sql , engine , feature ):
"""
:param sql: sql语句
:param engine: mysql引擎
:param feature: 数据特征
:return: 特征,数量
"""
df_sql = sql
df = pd . read_sql ( df_sql , engine )
df [ feature ] = df [ feature ]. astype ( str )
if feature == 'floor' :
df [ df [ feature ] == '暂无数据' ] = '18'
df [ feature ] = df [ feature ]. apply ( lambda x : re . findall ( 'd+' , x )[ 0 ])
feature_res = df [ feature ]. value_counts ()
index = feature_res . index
values = feature_res . values
return index , values
def multiple ( sql , engine , feature ):
"""
:param sql: sql语句
:param engine: mysql引擎
:param feature: 数据特征
:return: 特征,数量,总价均价,单价均价
"""
df_sql = sql
df = pd . read_sql ( df_sql , engine )
df [ feature ] = df [ feature ]. astype ( str )
if feature == 'region' :
df [ feature ] = df [ feature ]. apply ( lambda x : re . sub ( r"[|]|'" , '' , x ). split ( ',' )[ 0 ])
feature_res = df [ feature ]. value_counts ()
index = feature_res . index
values = feature_res . values
price_mean = []
unit_price_mean = []
max_unit_price = []
min_unit_price = []
for inx , val in zip ( index , values ):
price_mean . append ( format ( df [ df [ feature ] == inx ][ 'price' ]. astype ( float ). mean (), '.3f' ))
unit_price_mean . append ( format ( df [ df [ feature ] == inx ][ 'unit_price' ]. astype ( float ). mean (), '.3f' ))
max_unit_price . append ( format ( df [ df [ feature ] == inx ][ 'unit_price' ]. astype ( float ). max (), '.3f' ))
min_unit_price . append ( format ( df [ df [ feature ] == inx ][ 'unit_price' ]. astype ( float ). min (), '.3f' ))
return index , values , price_mean , unit_price_mean , max_unit_price , min_unit_price
프론트엔드는 기본 프레임워크인 Vue를 사용하여 구축되었으며, vue-admin-template을 기반으로 개발되었습니다. 주요 프론트 엔드 사업은 차트 표시 및 지도 정보에 주로 사용됩니다. 전반적인 정보 표시, 주택 장식 상태, 차트 정보 시각화 등 다양한 특성을 포함한 청두 중고 주택 가격 데이터 및 관련 정보를 시각화하는 데 주로 사용됩니다. 주택 검색, 기본 주택 소스 정보 시각화, 지도 표시 및 기타 기능.
프로젝트 설명
프런트엔드 프로젝트 파일 트리:
특정 프로세스
# 安装到项目
npm install -S element-ui
# 全局加载: 在main.js中
import ElementUI from ' element-ui '
import ' element-ui/lib/theme-chalk/index.css '
import locale from ' element-ui/lib/locale/lang/en ' // i18n
Vue.use(ElementUI, { locale })
# 这样我们据可以在vue文件中使用UI组件,如: 按钮组件<Button />
# 安装到项目
npm install -S vue-baidu-map
# 局部加载: 在任意vue文件中
import BaiduMap from ' vue-baidu-map/components/map/Map.vue '
# 地图标注组件
import { BmMarker } from ' vue-baidu-map '
# 使用,例如:
< baidu-map ref= " baidumap "
class= " bm-view "
style= " width:100%;height:93vh "
ak= " YOUR AK "
:center= " {lng: 104.07, lat: 30.67} "
:scroll-wheel-zoom= " true "
:zoom= " 16 " >
< bm-navigation anchor= " BMAP_ANCHOR_TOP_LEFT " ></bm-navigation >
< bm-marker v-for= " spoi in searchResults " -- >
< ! --:position= " {lng: spoi.lng, lat: spoi.lat} " -- >
< ! --:title= " spoi.community_name " > -- >
< /bm-marker >
< /baidu-map >
src 디렉토리에 새로운 폴더 utils를 생성하여 사용자 정의된 도구 파일을 저장하고, 새로운 request.js를 생성하고, 인터페이스를 요청할 때 작업을 충족하도록 axios 메소드를 다시 캡슐화합니다. 이 파일은 고급 인터페이스 액세스 방법으로 비동기 요청에 사용되는 개체를 노출합니다. 이 방법은 인터페이스 파일 디자인에 사용됩니다.
# 新建一个servie对象
const service = axios . create ( {
// 项目基本配置文件中定义的VUE_APP_BASE_API,开发和发布环境各部相同,在.env.devolopment和.env.product文件中进行配置
baseURL : process . env . VUE_APP_BASE_API ,
// 直接写死的url,无论环境如何都不会改变
// baseURL: 'http://127.0.0.1:8000/v1/api',
timeout : 8000 // 最大请求时间
} )
service . interceptors . request . use ( config => {
// 请求方法为POST时,配置请求头
config . method === 'post'
? config . data = qs . stringify ( { ... config . data } )
: config . params = { ... config . params }
config . headers [ 'Content-Type' ] = 'application/x-www-form-urlencoded'
return config
} , error => {
// 错误时提示信息
Message ( {
message : error ,
type : 'error' ,
duration : 3 * 1000
} )
Promise . reject ( error )
} )
service . interceptors . response . use (
response => {
if ( response . statusText === 'OK' ) {
// 正确响应时,直接返回数据
return response . data
} else {
Notification ( {
title : '错误' ,
message : '访问api错误' , // TODO 后端拦截错误请求
type : 'error' ,
duration : 3 * 1000
} )
}
} ,
// 错误处理
error => {
let dtext = JSON . parse ( JSON . stringify ( error ) )
try {
if ( dtext . response . status === 404 ) {
Notification ( {
type : 'error' ,
title : '出问题404' ,
message : '访问api错误或服务器忙' ,
duration : 3 * 1000
} )
}
} catch ( err ) {
Notification ( {
title : '错误' ,
message : '请求超时,请稍后再试或刷新页面' ,
type : 'error' ,
duration : 3 * 1000
} )
}
return Promise . reject ( error )
}
)
export default service
vue-router를 사용하여 라우팅 기능을 구현하고, 새로운 router.js를 생성하고, 라우팅 항목을 구성합니다.
// 定义路由列表
export const constantRoutes = [
{
path : '/404' ,
component : ( ) => import ( '@/views/404' ) ,
hidden : true
} ,
{
path : '/' ,
component : Layout ,
redirect : '/dashboard' ,
children : [ {
path : 'dashboard' ,
name : 'Dashboard' ,
// 采用路由懒加载的方式
component : ( ) => import ( '@/views/dashboard/index' ) ,
meta : { title : '房源信息' , icon : 'house' }
} ]
} ,
// ......
]
// 构造路由对象
const createRouter = ( ) => new Router ( {
// mode: 'history', // require service support
scrollBehavior : ( ) => ( { y : 0 } ) ,
routes : constantRoutes
} )
// 在main.js 中引入并挂载
import router from './router'
new Vue ( {
el : '#app' ,
router ,
render : h => h ( App )
} )
인터페이스 api.js 파일은 프로젝트와 관련된 모든 인터페이스 액세스 요청을 정의하며 함수로 캡슐화되어 구성 요소에서 사용할 수 있도록 노출됩니다.
// 例如获取区划信息接口
export function getRegionInfo ( id , params ) {
return request ( {
url : '/region/' + id ,
method : 'get' ,
params
} )
}
요청은 위에 캡슐화된 인터페이스 액세스 개체로, 사용하기 전에 가져와야 합니다. 구성 요소에서 인터페이스를 사용할 때 요청 시 가져옵니다. 예:
// charts.js是接口函数文件
import { getRegionInfo } from '@/api/charts'
// 使用
// 第一种方式,由于它本来就是一个promise对象,所以直接在then中获取响应
getRegionInfo ( 1 ) . then ( ( res , err ) => {
// 相关操作
} )
// 第二种方式,使用async await方式,包含await的方法中需要加上async 作为方法前缀
let res = await getRegionInfo ( 1 )