利用python爬取深圳市百度慧眼人口热力图数据,线形回归分析对爬取坐标进行转换,最后对爬取数据进行可视化展示。
数据爬取
深圳市百度慧眼人口热力图:http://huiyan.baidu.com/cms/heatmap/shenzhen.html (网址获取来源:利用App Store的“HTTP Traffic”工具对 “i深圳”APP中的“人流热力图”功能进行抓包分析所得,具体操作可查看APP内置教程,这里不再赘述)。
网址后缀为shenzhen,理论上还可以爬取其他城市,如wuhan、shanghai、beijing等。截止目前,查找相关报道得知,百度慧眼只开放了深圳、上海两个城市数据接口,后续大家可自行尝试其他城市。
另外,还请大家不要对端口进行恶意攻击或高频访问,一次请求即可获取全市数据,而且数据按小时更新。
以chrome浏览器为例,打开深圳地区百度慧眼人口热力图网址,按F12进入开发者模式,点击“Network”并刷新网页,查看网络连接解析。按文件大小进行排序,第一个即为所需数据(第二个是深汕合作区的数据,对应的cityId=440300_255_1),对其单击右键 Copy/Copy as cURL(bash),复制为curl。
打开“curl 转 python requests网站”:https://curl.trillworks.com/,将curl在线转化为requests请求。
获取requests代码如下
import requests
headers = {
'Connection': 'keep-alive',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36',
'Accept': '*/*',
'Origin': 'http://huiyan.baidu.com',
'Sec-Fetch-Site': 'cross-site',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Dest': 'empty',
'Referer': 'http://huiyan.baidu.com/cms/heatmap/shenzhen.html',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
}
params = (
('cityId', '440300'),
('ak', '3gHV4PFiWyZ7xnwnVjDq6vEel99V3jQc'),
)
response = requests.get('https://huiyan.baidu.com/openapi/v1/heatmap/heatmapsearch', headers=headers, params=params)
#NB. Original query string below. It seems impossible to parse and
#reproduce query strings 100% accurately so the one below is given
#in case the reproduced version is not "correct".
# response = requests.get('https://huiyan.baidu.com/openapi/v1/heatmap/heatmapsearch?cityId=440300&ak=3gHV4PFiWyZ7xnwnVjDq6vEel99V3jQc', headers=headers)
对爬取数据进行结构解析,发现数据储存形式为“横坐标_纵坐标_地名计数”,并且每个值由“|”分隔。
# coding=utf-8
import pandas as pd
# 将数据转化为dataframe格式
js1= response.json() # 获取响应文件的json格式
js2=js1['result']['data'] # 提取data部分
str1 = js2.split("|") # 以“|”符号对data进行分隔
df = pd.DataFrame(str1) # 传入dataframe
# 利用str.split()将数据分为多列
df['x'] = df[0].str.split('_').str[0]
df['y'] = df[0].str.split('_').str[1]
df['value'] = df[0].str.split('_').str[2]
需要注意的是,由于“data”是以 “|”结尾,df最后一行为空值,需要对其剔除。
# 剔除含有空值的行
df = df.dropna()
得到最终结果:
坐标转换
我们注意到,这里得所到的横纵坐标为{126XXXXX,25XXXXX}形式,根据网页源码(coordType: ‘bd09mc’)可知这是百度地图墨卡托坐标,需要转换为百度经纬度坐标。如果大家后续用echarts做可视化,也可不用转换,直接将坐标系参数设置为’bd09mc’。
由于百度墨卡托坐标转换需要调用百度JavaScript的API,实现起来比较麻烦,这里我们利用坐标映射的方法做近似转换。首先,利用excel的PowerMap功能对既有数据进行可视化展示(这里主要考虑到数据量不大,做轻量化分析即可,当然,也可以根据自己熟悉的其他工具展示,如ARCGIS、QGIS、echarts等),发现整体形状与深圳城市肌理相似,据此判断我们得到的横纵坐标与经纬度坐标存在线性相关。
将地图缩放到最大级别,对比发现存在一些识别度较高的孤点
我们找出一系列映射点,利用百度拾取坐标系统:http://api.map.baidu.com/lbsapi/getpoint/index.html,获取映射点的百度经纬度坐标,形成一系列坐标映射关系表:
LNG | LAT | X | Y |
---|---|---|---|
113.904579 | 22.656801 | 12679967 | 2573996 |
113.790251 | 22.676882 | 12667228 | 2576368 |
114.572617 | 22.505705 | 12754302 | 2555841 |
114.284261 | 22.801803 | 12722205 | 2591362 |
113.931434 | 22.848654 | 12682916 | 2596991 |
113.882373 | 22.853966 | 12677486 | 2597634 |
利用pandas、sklearn模块对坐标映射表进行回归分析:
1、读取坐标映射表
# 复制上表,读取剪切板数据
data = pd.read_clipboard()
我们查看一下各列数据之间的相关性,发现“LNG与X”以及“LAT与Y”之间相关性接近1,说明他们之间线性相关较强,印证了前面的猜想,理论上我们选取的映射坐标对越多,精度越高。
# 相关性系数分析
data.corr()
2、利用sklearn进行回归分析
- 对经度和横坐标拟合:
代码参考《Python回归分析五部曲(一)—简单线性回归》:https://www.cnblogs.com/shujufenxi/p/9054439.html
from sklearn.linear_model import LinearRegression
#估计模型参数,建立回归模型
'''
(1) 首先导入简单线性回归的求解类LinearRegression
(2) 然后使用该类进行建模,得到lrModel的模型变量
'''
lrModel = LinearRegression()
#(3) 接着,我们把自变量和因变量选择出来
x = data[['X']]
y = data[['LNG']]
#模型训练
'''
调用模型的fit方法,对模型进行训练
这个训练过程就是参数求解的过程
并对模型进行拟合
'''
lrModel.fit(x,y)
#对回归模型进行检验
print('决定系数(R^2):',lrModel.score(x,y))
#查看截距
alpha = lrModel.intercept_[0]
#查看参数
beta = lrModel.coef_[0][0]
print('alpha值:',alpha," beta值:",beta)
输出结果:
决定系数(R^2): 0.9999998457083179
alpha值: -0.027743944995606284 beta值: 8.98523370006391e-06
得到经度与横坐标之间的联系:
经度:lng = -0.027743944995606284 + 8.98523370006391e-06 * x
- 同样,对纬度和纵坐标拟合:
lrModel_2 = LinearRegression()
x_2 = data[['Y']]
y_2 = data[['LAT']]
#模型训练
'''
调用模型的fit方法,对模型进行训练
这个训练过程就是参数求解的过程
并对模型进行拟合
'''
lrModel_2.fit(x_2,y_2)
#对回归模型进行检验
print('决定系数(R^2):',lrModel_2.score(x,y))
#查看截距
alpha_2 = lrModel_2.intercept_[0]
#查看参数
beta_2 = lrModel_2.coef_[0][0]
print('alpha_2值:',alpha_2," beta_2值:",beta_2)
输出结果:
决定系数(R^2): 0.9999993702024847
alpha_2值: 1.20316833550093 beta_2值: 8.33483092560397e-06
得到纬度与纵坐标之间的联系:
纬度:lat = 1.20316833550093 + 8.33483092560397e-06 * y
3、坐标转换输出
# 添加百度坐标
df['lng'] = alpha+beta*df['x'].astype('f')
df['lat'] = alpha_2+beta_2*df['y'].astype('f')
# 剔除首列
df = df.drop([0],axis=1)
# 获取当前时间,请求结果中的time字段
time = js1['result']['time']
#输出到csv文件,结合自己所需文件编码类型,自行调整encoding的值
df.to_csv('./data/'+time+ '.csv',index=0,encoding='utf-8')
完整代码
# coding=utf-8
import requests
import pandas as pd
import json
from sklearn.linear_model import LinearRegression
import time
def req_data(url):
'''
数据爬取,返回json类结构
'''
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0',
'Accept': '*/*',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Referer': 'http://huiyan.baidu.com/cms/heatmap/shenzhen.html',
'Origin': 'http://huiyan.baidu.com',
'DNT': '1',
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0',
}
params = (
('cityId', '440300'),
('ak', '3gHV4PFiWyZ7xnwnVjDq6vEel99V3jQc'),
)
try:
r = requests.get(url=url, headers=headers, params=params)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.json()
except:
return "网络传输异常"
def linear_regression(df_1,df_2):
'''
对df_1,df_2两个单列进行线性回归分析
'''
lrModel = LinearRegression() # 构建线性回归模型
lrModel.fit(df_1,df_2) # 拟合
#对回归模型进行检验
print('决定系数(R^2):',lrModel.score(df_1,df_2))
#查看截距
alpha = lrModel.intercept_[0]
#查看参数
beta = lrModel.coef_[0][0]
return alpha,beta
def clean_data(json):
'''
数据整理,对返回数据进行重构
'''
data_cell = json['result']['data'].split("|")
df = pd.DataFrame(data_cell)
# 以‘_’为分隔符,拆分为多列,并删除首列
df['x'] = df[0].str.split('_').str[0]
df['y'] = df[0].str.split('_').str[1]
df['value'] = df[0].str.split('_').str[2]
df = df.drop([0],axis=1)
# 相应文件的‘data’以“|”结尾,需剔除最后一行
df = df.dropna()
# 传入映射坐标列表,格式为{百度坐标系经度,百度坐标系维度,横坐标,纵坐标}
data = pd.read_csv('./data/mapping_coordinates.csv')
# 拟合映射坐标
alpha_1,beta_1 = linear_regression(data[['X']],data[['LNG']])
alpha_2,beta_2 = linear_regression(data[['Y']],data[['LAT']])
# 添加百度坐标
df['lng'] = alpha_1+beta_1*df['x'].astype('f')
df['lat'] = alpha_2+beta_2*df['y'].astype('f')
return df
def main():
url = r"https://huiyan.baidu.com/openapi/v1/heatmap/heatmapsearch" # 深圳市百度慧眼人流热力图网址
js = req_data(url)
# 获取当前时间,请求结果中的time字段
global file_time
file_time = js['result']['time']
df = clean_data(js)
#输出到csv文件,结合自己所需文件编码类型,自行调整encoding的值
df.to_csv('./data/'+file_time+'.csv',index=0,encoding='utf-8')
if __name__ == '__main__':
while True:
main()
with open('spider.log','a',encoding='utf-8') as f:
f.write(file_time + '已录入\n')
time.sleep(3600)
- 注:mapping_coordinates.csv文件格式如下