Plataforma de visualización de datos del lado web basada en datos del rastreador de araña conjunta, acceso en línea
La fuente de datos proviene del proyecto Joint-spider.
Servidor: django-restframework
Interfaz: 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/
Detalles de la lista de viviendas comunitarias
Buscar página de propiedades
Página de detalles de propiedad específica
alrededor de la casa
# 项目依赖
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
Proceso básico
Ciclo de vida de Django:
El front-end envía una solicitud-->wsgi de Django-->middleware-->sistema de enrutamiento-->ver-->operación de base de datos ORM-->plantilla-->devolver datos al usuario
ciclo de vida del marco de descanso:
El front-end envía una solicitud-->wsgi de Django-->Middleware-->Sistema de enrutamiento_Ejecutar as_view () de CBV, que es ejecutar el método de dispath interno-->Antes de ejecutar dispath, hay un análisis de versión y un renderizador--> En dispath, encapsule la solicitud-->Versión-->Autenticación-->Permisos-->Limitar actual-->Ver-->Si la vista usa almacenamiento en caché (request.data o request.query_params ) usa el analizador -> la vista procesa los datos y usa la serialización (serializar o verificar los datos) -> la vista devuelve los datos usando paginación.
Árbol de archivos del proyecto:
# 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 " ,
}
}
}
Nota: Este proyecto es un desarrollo de interfaz y no involucra plantillas ni recursos estáticos, por lo que no es necesario configurar estos dos elementos.
Ingrese HouseAnalysisHouseAnalysisurls.py y configure la información de enrutamiento:
Agregue path('xadmin/', xadmin.site.urls)
, re_path(r'docs/', include_docs_urls(title="HA"))
a la lista de urlpatterns
, xadmin es el enrutamiento de la página de administración,
Docs es la interfaz de enrutamiento de documentos, a la que se puede acceder a través de http://localhost:8000/docs/, como se muestra a continuación:
El diseño del modelo Django es uno de los cinco diseños centrales básicos de Django (diseño del modelo, configuración de URL, escritura de vistas, diseño de plantillas, uso) y también es un vínculo importante en el modelo MVC (MVT).
El modelo es un modelo de datos, no una base de datos. Describe la estructura de los datos y la relación lógica entre ellos. En Django, el diseño del modelo es esencialmente una clase. Cada modelo se asigna a una tabla de base de datos.
Ingrese HouseAnalysishousemodels.py y agregue los modelos Api,Elevator,Floor,Layout,Region,Decortion,Purposes,Orientation,Constructure,Community,CommunityRange
para proporcionar datos para la serialización.
Una vez creado el modelo, realice la migración de datos:
# 进入项目根目录
python manage.py makemigrations
# 同步至数据库
python manage.py migrate
Cree un nuevo archivo serializer.py en la carpeta de la casa para serializar los datos.
Defina un serializador (esencialmente una clase), que generalmente incluye campos de la clase modelo y tiene sus propias reglas de tipo de campo. Después de implementar el serializador, puede crear objetos serializados y conjuntos de consultas para operaciones de serialización, y obtener datos a través de object.data serializados (no es necesario que construya un diccionario usted mismo y luego devuelva datos Json)
Cuando se usa para serialización, pase el objeto de clase del modelo al parámetro de instancia
Cuando se utiliza para la deserialización, pase los datos que se van a deserializar al parámetro de datos .
Además de los parámetros de instancia y datos, al construir el objeto Serializador, también puede agregar datos adicionales a través del parámetro de contexto .
Aquí utilizamos el serializador de modelos (ModelSerializer), que es el mismo que el serializador normal, pero proporciona:
Por ejemplo: el contenido de la casa se puede serializar usando un código simple
class HouseSerializer ( serializers . ModelSerializer ):
class Meta :
# 需要序列化的model
model = Api
# 需要序列化的字段
fields = "__all__"
De la misma manera, serialice los modelos restantes, incluidos ElevatorSerializer,FloorSerializer,LayoutSerializer,RegionSerializer,RegionS,CommunitySerializer,CommunityRangeSerializer,DecortionSerializer,PurposesSerializer,ConstructureSerializer,OrientationSerializer
, entre los cuales RegionS
sirve como serializador superior de CommunitySerializer
y CommunityRangeSerializer
.
La función de la vista: para decirlo sin rodeos, es recibir solicitudes de front-end y realizar procesamiento de datos.
(El procesamiento aquí incluye: si el front-end es una solicitud GET, construye un conjunto de consultas y devuelve los resultados. Este proceso es serialización ; si el front-end es una solicitud POST, si desea realizar cambios en la base de datos, necesita obtener los datos enviados por el front-end. Verificar y escribir los datos en la base de datos. Este proceso se llama deserialización ).
La vista más original puede implementar dicho procesamiento lógico, pero para diferentes solicitudes, es necesario definir varios métodos en la vista de clases para implementar su propio procesamiento. Esto puede resolver el problema, pero hay un defecto, es decir, el método general en. cada función La lógica es similar: leer la solicitud, obtener datos de la base de datos, escribir cosas en la base de datos y devolver los resultados al front-end. Esto producirá una gran cantidad de código duplicado.
En las vistas de desarrollo de API REST, aunque las operaciones de datos específicas de cada vista son diferentes, el proceso de implementación de agregar, eliminar, modificar y verificar es básicamente rutinario, por lo que esta parte del código también se puede reutilizar y simplificar para escribir:
Agregado : Verificar datos de solicitud -> Ejecutar proceso de deserialización -> Guardar base de datos -> Serializar y devolver el objeto guardado
Eliminar : determine si los datos que se eliminarán existen -> realizar la eliminación de la base de datos
Modificación : Determinar si los datos a modificar existen -> verificar los datos solicitados -> realizar el proceso de deserialización -> guardar la base de datos -> serializar y devolver el objeto guardado
Consulta : consultar la base de datos -> serializar y devolver los datos
Las vistas se escriben en el archivo views.py y aquí utilizamos diagramas de reconocimiento de clases para el desarrollo.
http://127.0.0.1/api/v1/allhouse
, debemos devolver y exponer los resultados serializados de toda la información de la casa.http://127.0.0.1/api/v1/allhouse
/1, debemos cambiar la identificación de la casa (la identificación en). este tiempo se puede configurar como Especificar el parámetro de requisito) y el resultado de serialización de 1 se devuelve y se expone.{id:xxx,values:xxx...}
, debemos verificar la información actual y agregar los datos actuales. la base de datos si es legal.ejemplo:
# 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
Otras vistas son similares, incluidas: ElevaorViewSet,FloorViewSet,LayoutViewSet,RegionViewSet,CommunityViewSet,CommunityPagination,AllCommunityViewSet,CommunityRangeViewSet,DecortionViewSet,PurposesViewSet,ConstructureViewSet,OrientationViewSet
Para la clase CommunityViewSet
, debemos devolver toda su información según el community_id específico. En este momento, no podemos usar el método retrieve
de la clase RetrieveModelMixin
. Necesitamos anular el método get_queryset
para devolver un modelo específico.
def get_queryset ( self ):
if self . request . GET [ 'rid' ] == '0' :
# 获取所有
return Community . objects . all ()
else :
return Community . objects . filter ( region = self . request . GET [ 'rid' ])
Otros parámetros
Utilice DefaultRouter
de rest_framework
para el registro de rutas:
from rest_framework . routers import DefaultRouter
router = DefaultRouter ()
router . register ( r'v1/api/all_house' , HouseListViewSet , base_name = "house" )
......
Agregue el enrutador a los patrones de URL:
path ( '' , include ( router . urls )),
En este momento, cuando una solicitud accede a la ruta existente, la ruta reenviará la solicitud a la clase de vista. Después de procesar la solicitud en la clase de vista, se devuelve el resultado serializado, que son los datos de la interfaz expuestos por el backend.
[La transferencia de la imagen del enlace externo falló. El sitio de origen puede tener un mecanismo anti-leeching. Se recomienda guardar la imagen y cargarla directamente (img-JmH9EULX-1588257851195) (imgs/interface data.png)]
Descripción de datos
Código de estado 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
El front-end se construye utilizando el marco básico Vue y se desarrolla en base a vue-admin-template. El negocio principal de front-end se utiliza para la visualización de gráficos e información de mapas. Se utiliza principalmente para visualizar datos de precios de viviendas de segunda mano en Chengdu e información relacionada, incluida la visualización de información general y diversas características, como el estado de la decoración de la casa y la visualización de información de gráficos. búsqueda de vivienda, visualización de información básica de fuentes de vivienda, visualización de mapas y otras funciones.
Descripción del proyecto
Árbol de archivos del proyecto front-end:
Proceso específico
# 安装到项目
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 >
Cree una nueva carpeta utils en el directorio src para almacenar nuestros archivos de herramientas personalizados, cree un nuevo request.js y vuelva a encapsular el método axios para cumplir con las operaciones cuando solicitamos la interfaz. Este archivo expondrá un objeto utilizado para solicitudes asincrónicas como un método de acceso a la interfaz más avanzado. Este método se utilizará en el diseño del archivo de interfaz.
# 新建一个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
Utilice vue-router para implementar la función de enrutamiento, crear un nuevo router.js y configurar elementos de enrutamiento.
// 定义路由列表
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 )
} )
El archivo de interfaz api.js define todas las solicitudes de acceso a la interfaz relacionadas con el proyecto, se encapsula en funciones y se expone para que los utilicen los componentes.
// 例如获取区划信息接口
export function getRegionInfo ( id , params ) {
return request ( {
url : '/region/' + id ,
method : 'get' ,
params
} )
}
La solicitud es el objeto de acceso a la interfaz encapsulado anteriormente, que debe importarse antes de su uso. Cuando utilice la interfaz en el componente, impórtelo según demanda, por ejemplo:
// charts.js是接口函数文件
import { getRegionInfo } from '@/api/charts'
// 使用
// 第一种方式,由于它本来就是一个promise对象,所以直接在then中获取响应
getRegionInfo ( 1 ) . then ( ( res , err ) => {
// 相关操作
} )
// 第二种方式,使用async await方式,包含await的方法中需要加上async 作为方法前缀
let res = await getRegionInfo ( 1 )