ジョイントスパイダー クローラー データ、オンライン アクセスに基づく Web 側データ視覚化プラットフォーム
データ ソースは、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 のライフサイクル:
フロントエンドはリクエストを送信します--> Django の wsgi--> ミドルウェア--> ルーティング システム--> 表示--> ORM データベース操作--> テンプレート--> ユーザーにデータを返します
REST フレームワークのライフサイクル:
フロントエンドはリクエストを送信します--> Django の wsgi--> ミドルウェア--> ルーティング システム_内部 dispath メソッドを実行する CBV の as_view() を実行します--> 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 " ,
}
}
}
注: このプロジェクトはインターフェイス開発であり、テンプレート テンプレートと静的リソースは含まれないため、これら 2 つの項目を構成する必要はありません。
HouseAnalysisHouseAnalysisurls.py と入力し、ルーティング情報を構成します。
path('xadmin/', xadmin.site.urls)
、 re_path(r'docs/', include_docs_urls(title="HA"))
urlpatterns
リストに追加します。xadmin は管理ページのルーティングです。
Docs はドキュメント ルーティング インターフェイスであり、以下に示すように http://localhost:8000/docs/ を通じてアクセスできます。
Django モデル設計は、Django の 5 つの基本的なコア設計 (モデル設計、URL 構成、ビュー作成、テンプレート設計、使用開始) の 1 つであり、MVC (MVT) モデルにおける重要なリンクでもあります。
モデルはデータ モデルであり、データベースではありません。Django では、モデル設計は基本的にクラスです。各モデルはデータベース テーブルにマップされます。
HouseAnalysishousemodels.py と入力し、 Api,Elevator,Floor,Layout,Region,Decortion,Purposes,Orientation,Constructure,Community,CommunityRange
モデルを追加して、シリアル化用のデータを提供します。
モデルの作成後、データ移行を実行します。
# 进入项目根目录
python manage.py makemigrations
# 同步至数据库
python manage.py migrate
データをシリアル化するために、house フォルダーに新しいファイル Serializer.py ファイルを作成します。
シリアライザー (本質的にはクラス) を定義します。これには通常、モデル クラスのフィールドが含まれ、独自のフィールド タイプ ルールがあります。シリアライザーを実装した後、シリアル化されたオブジェクトとシリアル化操作用のクエリ セットを作成し、シリアル化された object.data を通じてデータを取得できます (自分でディクショナリを構築して Json データを返す必要はありません)。
シリアル化に使用する場合は、モデル クラス オブジェクトをインスタンスパラメーターに渡します。
逆シリアル化に使用する場合は、逆シリアル化するデータをdataパラメーターに渡します。
Serializer オブジェクトを構築するときは、インスタンス パラメーターとデータ パラメーターに加えて、 contextパラメーターを使用して追加のデータを追加することもできます。
ここではモデル シリアライザー (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
クラスの場合、特定のコミュニティ 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" )
......
ルーターを URL パターンに追加します。
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 )