目前几个大厂,高德百度腾讯,都支持POI爬取,而AOI是需要自己找接口的。
换言之,爬虫需谨慎
参考链接是:
这两个链接是选定范围爬取范围内选定类别的AOI
黑科技 | 百度地图抓取地块功能(上)
黑科技 | 百度地图获取地块功能属性(下)
而这个链接是用名称爬取。我参考的是这个:利用名称爬取百度AOI
https://www.cnblogs.com/zhangqinglan/p/13301425.html
跟Amap一样,其实是找到了一个接口:
https://map.baidu.com/?newmap=1&qt=s&da_src=searchBox.button&wd=‘+Name+’&c=XXX
其中Name为要搜索的名称。c=XXX的XXX为城市的city_id。这个之前爬风向标的时候已经看过了。
第一步是先从https://map.baidu.com/?newmap=1&qt=s&da_src=searchBox.button&wd='+Name+'&c=XXX
这个接口里找打aoi的id。
跟参考链接不一样的是,我的名字是上一篇爬虫博客中爬好的csv文件。所以这里改动了下,方便读取我的格式。
另外参考博客这个层层嵌套的for和try-except,乍一看挺唬人。
在测试时还有个问题,就是try-except有时候会报错,返回false,因此我没用它。
以及我开代理爬取时有时成功有时不成功,不知道会不会被锁ip
还有就是在request语句中,参考博客是用 verify=False
。后续调试代码时多爬几次就服务器报错了。所以也删掉了。gpt显示这个错误是跟SSH证书有关
。
import requests
import pandas as pd
from urllib.parse import quote
import time
import random
import json
HEADERS = {'Accept':'*/*',
'Accept-Encoding':'gzip, deflate, sdch, br',
'Accept-Language':'zh-CN,zh;q=0.8',
'Connection':'keep-alive',
'Cookie':'BAIDUID=C4D08149D7EE627DC037119413418CA3:FG=1;'\
'BIDUPSID=C4D08149D7EE627DC037119413418CA3; PSTM=1540284487;'\
'pgv_pvi=9789244416; BDUSS=GF-S3Y5c1MybnhoTkhwMUxyWEhHM3ZreW1'\
'UTURiQk1TUFllMWc5V1ZWeUVHNGhkRVFBQUFBJCQAAAAAAAAAAAEAAAA~7j4'\
'5ztLKx8DtuaTIyzMyMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'\
'AAAAAAAAAAAAAAAAAAAAAAAAAAISOYF2EjmBdV; session_id=1567581077599;'\
'session_name=; validate=31187; MCITY=-%3A; M_LG_UID=960425535;'\
'M_LG_SALT=2cf3bbbbd3e5466b66a2529c037b982b',
'Host':'map.baidu.com',
'Referer':'https://map.baidu.com/search/%E9%83%91%E5%B7%9E%E5%B8%82%E4'\
'%BA%8C%E4%B8%83%E4%B8%87%E8%BE%BE%E4%B8%89%E5%8F%B7%E9%99%A2/@'\
'12650489.832882352,4101769.7450000006,18.35z/maptype%3DB_EARTH_'\
'MAP?querytype=s&da_src=shareurl&wd=%E9%83%91%E5%B7%9E%E5%B8%82%'\
'E4%BA%8C%E4%B8%83%E4%B8%87%E8%BE%BE%E4%B8%89%E5%8F%B7%E9%99%A2&c'\
'=268&src=0&pn=0&sug=0&l=18&b=(12650755.24571287,4101867.11178217'\
'83;12651854.04020792,4102020.4126732675)&from=webmap&biz_forward='\
'%7B%22scaler%22:1,%22styles%22:%22pl%22%7D&device_ratio=1',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) '
'AppleWebKit/537.36 (KHTML, like Gecko)'
'Chrome/55.0.2883.87 Safari/537.36',}
table=pd.read_csv(r"文件")
result_df = pd.DataFrame()
# 依次读取每个城市的名称和内容
for city in table.columns:
city_name = city
if city_name=='XXX':
code=aaa;
elif city_name=='XXX':
code=aaa
city_content = table[city].values.tolist()
city_content = list(filter(lambda x: str(x) != "nan", city_content))
list_aoiid=[]
for mall in city_content:
encoded_mall = quote(mall)
url = "https://map.baidu.com/?newmap=1&qt=s&da_src=searchBox.button&wd={}&c={}".format(
encoded_mall, code)
print(url)
delay = random.randint(2, 5) # 生成1到2之间的随机整数
time.sleep(delay) # 延时随机秒数
r = requests.get(url,headers=HEADERS, allow_redirects=True)
data = r.content
data=data.decode('utf-8')
data = json.loads(data)
aoi_id = data['result']['profile_uid']
print(aoi_id)
list_aoiid.append(aoi_id)
city_data = pd.DataFrame({
city_name: city_content,
'aoiid': list_aoiid})
result_df = pd.concat([result_df, city_data], ignore_index=True, axis=1)
print(result_df)
可以看见最后获得的dataframe结果是这样的。是的我个人很喜欢搞完一步就存dataframe。
没有表头自己加一下就好。然后我们随机挑选几个进行验证。
验证链接就是前面代码print(url)
输出的链接,或者自己按照前面的方法,设置name和city id,也行。
也是很佩服参考链接的博主怎么找到这个是aoi id 的,以及发现aoi的接口
不知道博主从哪里找到的接口:
https://map.baidu.com/?newmap=1&qt=ext&uid=‘+AOI ID+’&ext_ver=new&ie=utf-8&l=1
更改里面的AOI ID就行。下图中的Geo就是我们需要的
根据黑科技 | 百度地图获取地块功能属性(下)链接中的讲解,
aoi抓取到的数据是百度米制坐标,也就是百度墨卡托投影坐标系。而我们一般使用的都是wgs84大地坐标系。
需要转换【百度米制】到【百度经纬度坐标】再到【wgs84】。也就是【bd09mc】到【bd09】到【wgs84】
也就是要将下图中右边各个点的坐标进行转换
这里的转换跟黑科技 | 百度地图获取地块功能属性(下)提供的下载包中的转换代码做了对比,【bd09mc】到【bd09】
没有差别,是一样的,而【bd09】到【wgs84】
有出入。看想要哪种吧。我懒得换了。
#from station import stations
import warnings
import xdrlib ,sys
import xlrd
import time
import socket
#bd墨卡托转BD-09
import math
pi = 3.1415926535897932384626
def Yr(lnglat,b):
if b!='':
c=b[0]+b[1]*abs(lnglat[0])
d=abs(lnglat[1]/b[9])
d=b[2]+b[3]*d+b[4]*d*d+b[5]*d*d*d+b[6]*d*d*d*d+b[7]*d*d*d*d*d+b[8]*d*d*d*d*d*d
if 0>lnglat[0]:
bd=-1*c
else:
bd=c
lnglat[0]=bd
if 0 > lnglat[0]:
bd2 = -1 * d
else:
bd2 = d
lnglat[1] = bd2
return lnglat
return
def Mecator2BD09(lng,lat):
lnglat=[0,0]
Au=[[1.410526172116255E-8, 8.98305509648872E-6, -1.9939833816331, 200.9824383106796, -187.2403703815547,
91.6087516669843, -23.38765649603339, 2.57121317296198, -0.03801003308653, 1.73379812E7],
[- 7.435856389565537E-9, 8.983055097726239E-6, -0.78625201886289, 96.32687599759846, -1.85204757529826,
-59.36935905485877, 47.40033549296737, -16.50741931063887, 2.28786674699375, 1.026014486E7],
[- 3.030883460898826E-8, 8.98305509983578E-6, 0.30071316287616, 59.74293618442277, 7.357984074871,
-25.38371002664745, 13.45380521110908, -3.29883767235584, 0.32710905363475, 6856817.37],
[- 1.981981304930552E-8, 8.983055099779535E-6, 0.03278182852591, 40.31678527705744, 0.65659298677277,
-4.44255534477492, 0.85341911805263, 0.12923347998204, -0.04625736007561, 4482777.06],
[3.09191371068437E-9, 8.983055096812155E-6, 6.995724062E-5, 23.10934304144901, -2.3663490511E-4,
-0.6321817810242, -0.00663494467273, 0.03430082397953, -0.00466043876332, 2555164.4],
[2.890871144776878E-9, 8.983055095805407E-6, -3.068298E-8, 7.47137025468032, -3.53937994E-6, -0.02145144861037,
-1.234426596E-5, 1.0322952773E-4, -3.23890364E-6, 826088.5]]
Sp=[1.289059486E7, 8362377.87, 5591021, 3481989.83, 1678043.12, 0 ]
lnglat[0]=math.fabs(lng)
lnglat[1] =abs(lat)
for d in range(0,6):
if lnglat[1]>=Sp[d]:
c=Au[d]
break
lnglat=Yr(lnglat,c)
return lnglat
def BD092WGS84(lnglat):
#bd09-gcj
x_pi = 3.14159265358979324 * 3000.0 / 180.0
pi = 3.1415926535897932384626 # π
a = 6378245.0 # 长半轴
ee = 0.00669342162296594323 # 扁率
x = lnglat[0] - 0.0065
y = lnglat[1] - 0.006
z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * x_pi)
theta = math.atan2(y, x) - 0.000003 * math.cos(x * x_pi)
lnglat[0] = z * math.cos(theta)
lnglat[1] = z * math.sin(theta)
dlat = tranlat1(lnglat[0] - 105.0, lnglat[1] - 35.0)
dlng = tranlng1(lnglat[0] - 105.0, lnglat[1] - 35.0)
radlat = lnglat[1] / 180.0 * pi
magic = math.sin(radlat)
magic = 1 - ee * magic * magic
sqrtmagic = math.sqrt(magic)
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
mglat = lnglat[1] + dlat
mglng = lnglat[0] + dlng
return [lnglat[0]* 2 - mglng, lnglat[1] * 2 - mglat]
def tranlat1(lng, lat):
ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * math.sqrt(math.fabs(lng))
ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 *
math.sin(2.0 * lng * pi)) * 2.0 / 3.0
ret += (20.0 * math.sin(lat * pi) + 40.0 *
math.sin(lat / 3.0 * pi)) * 2.0 / 3.0
ret += (160.0 * math.sin(lat / 12.0 * pi) + 320 *
math.sin(lat * pi / 30.0)) * 2.0 / 3.0
return ret
def tranlng1(lng, lat):
ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + \
0.1 * lng * lat + 0.1 * math.sqrt(math.fabs(lng))
ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 *
math.sin(2.0 * lng * pi)) * 2.0 / 3.0
ret += (20.0 * math.sin(lng * pi) + 40.0 *
math.sin(lng / 3.0 * pi)) * 2.0 / 3.0
ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 *
math.sin(lng / 30.0 * pi)) * 2.0 / 3.0
return ret
大体跟按照名称进行aoi提取的代码是一样的,没怎么改动。只不过原代码因为是id和aoi坐标嵌套的,所以最后输入坐标时可以直接用名称。我这里还需要自己再改改。
import requests
import pandas as pd
from urllib.parse import quote
import time
import random
import json
table=pd.read_csv(r"XXXXX")
none_aoiid=[]
with open(r"XXXXX",
'a',newline='') as f:
for z in range(0,4):
print('现在的数字是',z)
city_mall=table.iloc[:,2*z]
id_mall=table.iloc[:,2*z+1]
id_content = id_mall.values.tolist()
id_content = list(filter(lambda x: str(x) != "nan", id_content))
for aoiid in id_content:
url_AOI = 'https://map.baidu.com/?newmap=1&qt=ext&uid={}'\
'&ext_ver=new&ie=utf-8&l=11'.format(aoiid)
print('处理链接',url_AOI)
r_AOI = requests.get(url_AOI,headers=HEADERS, allow_redirects=True)
data = r_AOI.content.decode('utf-8')
data = json.loads(data)
if 'geo' in data['content']:
geo_AOI = data['content']['geo']
geo_AOI = geo_AOI.split('|')
point = geo_AOI[2].split(",")
point_transform=[]
for i in range(int(len(point)/2)):#全部点的坐标,分别是x,y,的形式
if i==0: #[2:]的作用:删除第一个坐标的‘1-’字符
point[2*i] = point[2*i][2:]
if i==int((len(point)/2)-1): #删除最后一个坐标的‘;’字符
point[2*i+1] = point[2*i+1][:-1]
# print('第'+str(i)+'个点的坐标',float(point[2*i]),float(point[2*i+1]))#打印出各点的坐标
point_Mecator2BD09 = Mecator2BD09(float(point[2*i]),float(point[2*i+1]))
point_BD092WGS84 = BD092WGS84(point_Mecator2BD09)
point_transform.append(point_BD092WGS84)
point_str = '' #这是创建一个文本存储
for j in range(len(point_transform)):
point_str = point_str+(str(point_transform[j])).replace(' ','')[1:-1]+';'
print(str(aoiid)+' 处理完毕。开始写入')
line=aoiid+' '+point_str+'\n'
f.write(line)
else:
print(aoiid+'没有找到坐标')
none_aoiid.append(aoiid)
print('没有信息的aoi',str(none_aoiid))
将txt的iaoiid和csv文件进行匹配替换,最后结果是下面这样
Amap的爬虫是我之前就做过的,用到就是下面的链接方法。需要一个个自己输入,比较麻烦。
但是抓包工具比自己的代码更强大,能搞定Amap的反扒机制。很强。
由于信息做过筛选所以百度这里只有坐标,最后生成AOI的shapefile的话也没有什么信息。这个还需要改进其实。毕竟信息都爬下来了。
1.2的代码中最后有输出一个没有爬取到的aoi_id信息
。而在对上一篇爬虫文里获得的百度风向标结果进行分析时看见,有一些地方的id是重复的。一开始我以为是触发了反扒机制,所以有些返回错误。刚刚对AOI生成的shapefile进行校验时发现,是百度地图的AOI有些不准确
,有的范围太大了把其他区域也包括进去了。所以可能返回的商场是相同的id。
经过我自己在高德和百度的实验,百度aoi的位置更准确,也就是前面的代码获得最后转wgs84的aoi在arcgis pro的底图中很贴合
。而高德使用下面的链接POI的体量 - AOI数据获取脚本分享获得不太准确,偏移较大。
所以下面链接的半自动化代码其实也是需要改进的。
这个是一个半自动化的抓取方式。用抓包工具Fiddler
POI的体量 - AOI数据获取脚本分享
这个链接现在会触发高德反扒滑动条,如果躲不了就无法用:Python批量爬取高德AOI边界数据+GIS可视化(超详细)