Plataforma de visualização de dados do lado da Web baseada em dados do joint-spider crawler, acesso on-line
A fonte de dados vem do projeto Joint-spider
Back-end: Django-restframework
Interface: 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/
Detalhes da lista de habitação comunitária
Pesquisar página de propriedades
Página de detalhes específicos da propriedade
Ao redor da 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
Processo básico
Ciclo de vida do Django:
O front-end envia uma solicitação -> wsgi do Django -> middleware -> sistema de roteamento -> visualização -> operação do banco de dados ORM -> modelo -> retornar dados ao usuário
ciclo de vida da estrutura de descanso:
O front end envia uma solicitação -> wsgi do Django -> Middleware -> Sistema de roteamento_Execute as_view() do CBV, que é para executar o método dispath interno -> Antes de executar o dispath, há análise de versão e renderizador -> Em dispath, encapsule a solicitação -> Versão -> Autenticação -> Permissões -> Limite atual -> Visualização -> Se a visualização usar cache (request.data ou request.query_params ) usa o analisador -> a visualização processa os dados e usa a serialização (serializando ou verificando os dados) -> a visualização retorna os dados usando paginação.
Árvore de arquivos do projeto:
# 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 projeto é um desenvolvimento de interface e não envolve modelos de templates e recursos estáticos, portanto não há necessidade de configurar esses dois itens.
Digite HouseAnalysisHouseAnalysisurls.py e configure as informações de roteamento:
Adicione path('xadmin/', xadmin.site.urls)
, re_path(r'docs/', include_docs_urls(title="HA"))
à lista de urlpatterns
, xadmin é o roteamento da página de gerenciamento,
Docs é a interface de roteamento de documentos, que pode ser acessada através de http://localhost:8000/docs/, conforme mostrado abaixo:
O design do modelo Django é um dos cinco designs básicos do Django (design do modelo, configuração de URL, escrita de visualização, design do modelo, uso) e também é um elo importante no modelo MVC (MVT).
Model é um modelo de dados, não um banco de dados. Ele descreve a estrutura dos dados e o relacionamento lógico entre eles. No Django, o design do modelo é essencialmente uma classe. Cada modelo é mapeado para uma tabela de banco de dados.
Insira HouseAnalysishousemodels.py e adicione os modelos Api,Elevator,Floor,Layout,Region,Decortion,Purposes,Orientation,Constructure,Community,CommunityRange
para fornecer dados para serialização.
Após a criação do modelo, execute a migração de dados:
# 进入项目根目录
python manage.py makemigrations
# 同步至数据库
python manage.py migrate
Crie um novo arquivo serializer.py na pasta inicial para serializar os dados.
Defina um serializador (essencialmente uma classe), que geralmente inclui campos da classe do modelo e possui suas próprias regras de tipo de campo. Depois de implementar o serializador, você pode criar objetos serializados e conjuntos de consultas para operações de serialização e obter dados por meio de object.data serializados (você não precisa construir um dicionário sozinho e depois retornar dados Json)
Quando usado para serialização, passe o objeto da classe do modelo para o parâmetro de instância
Quando usado para desserialização, passe os dados a serem desserializados no parâmetro data .
Além dos parâmetros de instância e de dados, ao construir o objeto Serializer, você também pode adicionar dados adicionais por meio do parâmetro de contexto .
Aqui usamos o serializador de modelo (ModelSerializer), que é igual ao serializador normal, mas fornece:
Por exemplo: o conteúdo da casa pode ser serializado usando código simples
class HouseSerializer ( serializers . ModelSerializer ):
class Meta :
# 需要序列化的model
model = Api
# 需要序列化的字段
fields = "__all__"
Da mesma forma, serialize os modelos restantes, incluindo ElevatorSerializer,FloorSerializer,LayoutSerializer,RegionSerializer,RegionS,CommunitySerializer,CommunityRangeSerializer,DecortionSerializer,PurposesSerializer,ConstructureSerializer,OrientationSerializer
, entre os quais RegionS
serve como serializador superior de CommunitySerializer
e CommunityRangeSerializer
.
A função da visualização: Para ser franco, é receber solicitações de front-end e realizar o processamento de dados.
(O processamento aqui inclui: se o front-end for uma solicitação GET, construir um conjunto de consultas e retornar os resultados. Este processo é serialização ; se o front-end for uma solicitação POST, se você quiser fazer alterações no banco de dados, você precisa obter os dados enviados pelo front-end, verificar e gravar os dados no banco de dados. Esse processo é chamado de desserialização .
A visão mais original pode implementar esse processamento lógico, mas para solicitações diferentes, vários métodos precisam ser definidos na visão da classe para implementar seu próprio processamento. Isso pode resolver o problema, mas há uma falha, ou seja, o método geral em. cada função A lógica é semelhante: ler a solicitação, obter dados do banco de dados, gravar coisas no banco de dados e retornar os resultados para o front end. Isso produzirá muito código duplicado.
Nas visualizações de desenvolvimento da API REST, embora as operações de dados específicas de cada visualização sejam diferentes, o processo de implementação de adição, exclusão, modificação e verificação é basicamente rotineiro, portanto, esta parte do código também pode ser reutilizada e simplificada para escrever:
Adicionado : Verifique os dados da solicitação -> Execute o processo de desserialização -> Salvar banco de dados -> Serialize e retorne o objeto salvo
Excluir : determine se os dados a serem excluídos existem -> execute a exclusão do banco de dados
Modificação : Determine se os dados a serem modificados existem -> verifique os dados solicitados -> execute o processo de desserialização -> salve o banco de dados -> serialize e retorne o objeto salvo
Consulta : Consulta o banco de dados -> Serializa e retorna os dados
As visualizações são escritas no arquivo views.py e aqui usamos diagramas de reconhecimento de classe para desenvolvimento.
http://127.0.0.1/api/v1/allhouse
, precisamos retornar e expor os resultados serializados de todas as informações da casa.http://127.0.0.1/api/v1/allhouse
/1, precisamos alterar o ID da casa (o ID em. desta vez pode ser configurado como O resultado da serialização que especifica o parâmetro de requisito) é 1 é retornado e exposto.{id:xxx,values:xxx...}
, precisamos verificar as informações atuais e adicionar os dados atuais a. o banco de dados se for legal.exemplo:
# 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
Outras visualizações são semelhantes, incluindo: ElevaorViewSet,FloorViewSet,LayoutViewSet,RegionViewSet,CommunityViewSet,CommunityPagination,AllCommunityViewSet,CommunityRangeViewSet,DecortionViewSet,PurposesViewSet,ConstructureViewSet,OrientationViewSet
Para a classe CommunityViewSet
, precisamos retornar todas as suas informações com base no community_id específico. Neste momento, não podemos usar o método retrieve
da classe RetrieveModelMixin
. Precisamos substituir o método get_queryset
para retornar um 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' ])
Outros parâmetros
Use DefaultRouter
de rest_framework
para registro de rota:
from rest_framework . routers import DefaultRouter
router = DefaultRouter ()
router . register ( r'v1/api/all_house' , HouseListViewSet , base_name = "house" )
......
Adicione o roteador aos urlpatterns:
path ( '' , include ( router . urls )),
Neste momento, quando uma solicitação acessa a rota existente, a rota encaminhará a solicitação para a classe de visualização. Após o processamento da solicitação na classe de visualização, o resultado serializado é retornado, que são os dados da interface expostos pelo backend.
[Falha na transferência da imagem do link externo. O site de origem pode ter um mecanismo anti-leeching. Recomenda-se salvar a imagem e carregá-la diretamente (img-JmH9EULX-1588257851195) (imgs/interface data.png)]
Descrição dos dados
Código de status 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
O front-end é construído usando o framework básico Vue e desenvolvido com base no vue-admin-template. O principal negócio front-end é usado para exibição de gráficos e informações de mapas. É usado principalmente para visualizar dados de preços de casas usadas em Chengdu e informações relacionadas, incluindo exibição de informações gerais, várias características - como status de decoração de casas, visualização de informações de gráficos, busca de moradia, moradia básica Visualização de informações de fonte, exibição de mapa e outras funções.
Descrição do projeto
Árvore de arquivos do projeto front-end:
Processo 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 >
Crie uma nova pasta utils no diretório src para armazenar nossos arquivos de ferramentas customizados, crie um novo requests.js e reencapsule o método axios para atender às operações quando solicitarmos a interface. Este arquivo irá expor um objeto usado para solicitações assíncronas como um método de acesso à interface mais avançado. Este método será usado no design do arquivo de interface.
# 新建一个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
Use vue-router para implementar a função de roteamento, crie um novo router.js e configure itens de roteamento.
// 定义路由列表
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 )
} )
O arquivo interface api.js define todas as solicitações de acesso à interface relacionadas ao projeto e é encapsulado em funções e exposto para uso pelos componentes.
// 例如获取区划信息接口
export function getRegionInfo ( id , params ) {
return request ( {
url : '/region/' + id ,
method : 'get' ,
params
} )
}
A solicitação é o objeto de acesso à interface encapsulado acima, que precisa ser importado antes do uso. Ao utilizar a interface no componente, importe-a sob demanda, por exemplo:
// charts.js是接口函数文件
import { getRegionInfo } from '@/api/charts'
// 使用
// 第一种方式,由于它本来就是一个promise对象,所以直接在then中获取响应
getRegionInfo ( 1 ) . then ( ( res , err ) => {
// 相关操作
} )
// 第二种方式,使用async await方式,包含await的方法中需要加上async 作为方法前缀
let res = await getRegionInfo ( 1 )