POI数据,英文全称Point of Intersesting,中文的意思是兴趣点,指的是在地图上有意义的点:比如商店、酒吧、加油站、医院、车站等。POI数据能够赋能时空行为、城市规划、地理信息等研究,因此获取准而全的POI数据是开展科研工作的基础。
高德POI大类有汽车服务 汽车销售 汽车维修 摩托车服务 餐饮服务 购物服务 生活服务 体育休闲服务 医疗保健服务 住宿服务 风景名胜 商务住宅 政府机构及社会团体 科教文化服务 交通设施服务 金融保险服务 公司企业 道路附属设施 地名地址信息 公共设施 事件活动 室内设施 通行设施。
首先要在高德开放平台申请key,每天各个接口查询都有限额,一般来讲够用。
本文以抓取成都市的餐饮服务业为例讲述。
这里调用高德的多边形接口来选定区域。
需要注意的大前提是高德官方文档的这句话:“另外,无论您指定多少个type,每次请求最多返回1000个POI信息,若场景需要获取更可能多的POI;建议您不要在type之中指定过多的类别,而是分多次请求从而得到更加准确的结果。”
也就是我们每次请求一个区域内最多返回1000条数据,所以为了得到全部数据,当返回数据大于1000时,必须把该区域拆分为多个区域再次查询,如果拆分的区域里仍然大于1000,则继续拆分,就是一个递归的过程,另外实际上最大值实测用800条更稳妥。这一块递归拆分的算法是全过程技术难度最大的算法,我是搞不定了,还好网上有大佬现成的算法拿来用就行。
多边形搜索POI这个接口关键点就是传入好网格经纬度参数,传入矩形左上角和右下角经纬度即可。
首先我们要获取成都市的区域边界,这也是一个多边形经纬度,高德官方也有这个接口。
https://restapi.amap.com/v3/config/district?keywords=%E6%88%90%E9%83%BD%E5%B8%82&subdistrict=0&extensions=all&key=<你的key>
把得到的这些坐标保存到桌面txt文件,只取得"polyline"中的经纬度即可。这些坐标中还会有“|”,把它换为换行。最终如图:
OK准备工作完成可以开始在Python里拆分区域网格了。
#这些包该导入的就导入 pip python install xxxxx
import requests
from shapely.geometry import Polygon
from shapely import wkt
from requests.adapters import HTTPAdapter
s = requests.Session()
s.mount('http://', HTTPAdapter(max_retries=3))#设置重试次数为3次
s.mount('https://', HTTPAdapter(max_retries=3))
# 获取矩形搜索到poi的count数量
# key,是高德地图的key
# r是矩形,[minlng,minlat,maxlng,maxlat]
def countSearchPoi(key,r):
minlng = r[0]
minlat = r[1]
maxlng = r[2]
maxlat = r[3]
#高德接口有bug,用中文类别搜比数字类别代码结果多,这是一个小坑
url = 'https://restapi.amap.com/v3/place/polygon?types=餐饮服务&offset=20&page=1&extensions=all&output=json&polygon='\
+ str(minlng) +',' + str(minlat) + '|' + str(maxlng) + ',' + str(maxlat) + '&key=' + key
count = -1
#print(url)
try:
r = s.get(url, timeout=5)
data = r.json()
if 'status' in data:
if data['status'] == '1':
count = int(data['count'])
except BaseException as e:
print(e)
if (count>800) :
print(url)
return count
# 切分矩形,把大矩形分成四个小矩形
def clipRectangle(r):
minlng = r[0]
minlat = r[1]
maxlng = r[2]
maxlat = r[3]
# dx = 0.5*(maxlng-minlng)
# dy = 0.5*(maxlat-minlat)
dx = 0.5*(maxlng-minlng)
dy = 0.5*(maxlat-minlat)
return [[minlng,minlat,minlng+dx,minlat+dy],[minlng+dx,minlat,maxlng,minlat+dy],
[minlng+dx,minlat+dy,maxlng,maxlat],[minlng,minlat+dy,minlng+dx,maxlat]]
# 按照步长,将面切分成矩形网格,把所有跟入参的面相交的矩形都返回
# polygon是多边形geometry,d是步长
def clipPolygon(polygon,d):
bbox = polygon.bounds
rectangles = []
lngnum = int((bbox[2]-bbox[0])/d)+1
latnum = int((bbox[3]-bbox[1])/d)+1
minlng = int(bbox[0]/d)*d
minlat = int(bbox[1]/d)*d
for i in range(0,lngnum+1):
for j in range(0,latnum+1):
rectangle = Polygon([(minlng+d*i,minlat+d*j),(minlng+d*i+d,minlat+d*j),
(minlng+d*i+d,minlat+d*j+d),(minlng+d*i,minlat+d*j+d),(minlng+d*i,minlat+d*j)])
if polygon.intersects(rectangle):
rectangles.append([minlng+d*i,minlat+d*j,minlng+d*i+d,minlat+d*j+d])
return rectangles
if __name__ =='__main__':
d = 0.03
#这个步长可以自己调整,大部分用的0.05
maxcount = 700
#这个就是设定网格返回POI数量最大值,大于这个值我们就认为这个网格需要拆分来获取更多精准点位
key = '你的key'
# 将成都的范围坐标点串从txt文件中读出,构建成polygon
# 成都的坐标点有多行,都要读取
# polygon这个包是构建多边形地图的
fpolyline = open(r'C:\\Users\\Administrator\\Desktop\\行政边界.txt','r',encoding='utf-8')
fnew = open(r'C:\\Users\\Administrator\\Desktop\\rectangles.txt','w+',encoding='utf-8',buffering=1)
for polyline in fpolyline.readlines():
polyline = polyline.strip()
polygonstr = 'POLYGON(('+polyline.replace(',',' ').replace(';',',')+'))'
print(polygonstr)
polygon = wkt.loads(polygonstr)
# 根据polygon和步长,构建矩形网格
rectangles = clipPolygon(polygon,d)
# 嵌入递归
for r in rectangles:
# 如果矩形构建的polygon与范围polygon不相交,进入下一个
rp = Polygon([(r[0],r[1]),(r[2],r[1]),(r[2],r[3]),(r[0],r[3]),(r[0],r[1])])
if not polygon.intersects(rp):
continue
count = countSearchPoi(key,r)
#print(count)
# 如果count小于阈值,将矩形和count写入文件,否则将矩形拆分,进行递归
if count < maxcount:
fnew.write(str(r[0])+','+str(r[1])+'|'+str(r[2])+','+str(r[3])+'\t'+str(count)+'\n')
else:
#print("我超过800啦")
rectangles.extend(clipRectangle(r))
fnew.close()
fpolyline.close()
这样一来,就会在桌面生成rectangles.txt这个文件,里面存储的就是拆分好了的网格和数量,我们就可以抓取这些网格里的数据并写入csv。
from asyncore import write
from sqlite3 import Timestamp
from time import time
import requests
from requests.adapters import HTTPAdapter
import math
import csv
import datetime
import pandas
s = requests.Session()
s.mount('http://', HTTPAdapter(max_retries=3))#设置重试次数为3次
s.mount('https://', HTTPAdapter(max_retries=3))
# 获取POI
# key,是高德地图的key
# r是矩形,minlng,minlat|maxlng,maxlat
# page是页码
def getPoi(key,r,page):
url = 'https://restapi.amap.com/v3/place/polygon?types=餐饮服务&offset=20&page='+str(page)+'&extensions=all&output=json&polygon='\
+ r + '&key=' + key
print(url)
result = []
try:
r = s.get(url, timeout=5)
data = r.json()
if 'status' in data:
if data['status'] == '1':
pois = data['pois']
for p in pois:
#按格子划分在边界可能采集到其他市数据,这里筛选掉
if (str(p['cityname']) == "成都市"):
result.append([str(p['name']),str(p['pname']),str(p['cityname']),str(p['adname']),str(p['address']),str(p['tel']),str(p['location']),str(p['id']),str(p['type']),str(p['typecode']),str(p['timestamp']),str(p['adcode']),str(p['business_area'])])
except BaseException as e:
print(e)
return result
if __name__ =='__main__':
key = '4e3e9cd261cc6dd7531938b00558d7d3'
f = open(r'C:\\Users\\Administrator\\Desktop\\rectangles.txt','r',encoding='utf-8')
# 1. 创建文件对象
filename = "C:\\Users\\Administrator\\Desktop\\抓取的高德POI_"+datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d_%H-%M-%S')+".csv"
#filename = "C:\\Users\\Administrator\\Desktop\\抓取的高德POI_2022-05-10_15-31-11.csv"
csvfile = open(filename,'a+',newline='',buffering=1,encoding='utf-8-sig')
writer = csv.writer(csvfile)
csvhead = ["名称","省份","城市","区县","详细地址","联系方式","经纬度","唯一ID","地点类型","地点类型代码","更新时间","所属区县代码","所属商圈"]
writer.writerow(csvhead)
flines = f.readlines()
f.close()
hang = 0
for l in flines:
print("已经完成"+(str)(hang)+"/"+str(len(flines)))
llist = l.strip('\n').split('\t')
r = llist[0]
count = int(llist[1])
hang = hang + 1
if count < 1:
continue
# 整除,向上取整
pagenum = math.ceil(count/20)
for i in range(1,pagenum+1):
result = getPoi(key,r,i)
if len(result) > 0:
for re in result:
#print(re)
# 2. 基于文件对象构建 csv写入对象
writer = csv.writer(csvfile)
# 3. 写入csv文件内容
writer.writerow(re)
#4. 关闭文件
csvfile.close()
#5. 去重,高德自己返回的数据里就有大量重复点位,需要用pandas的包去重
df = pandas.read_csv(filename)
df = df.drop_duplicates(subset='唯一ID', keep='last')
df.to_csv(filename)
这里代码层面就全部完成了,会在桌面到处一个csv如图:
这里面有几个需要注意的点:
1、因为行政区是不规则的多边形,按网格拆分在边缘部分必然会有其他城市区县的数据,所以在写入时就根据返回的城市参数做一次筛选,只写入成都的数据 if (str(p['cityname']) == "成都市"):
2、高德自己返回的数据里就有大量重复点位,需要用pandas的包去重
df = pandas.read_csv(filename)
df = df.drop_duplicates(subset='唯一ID', keep='last')
df.to_csv(filename)
第一个坑,返回的count是不正确的,实测count越大准确率越低,多次测试发现count最大值为70时候count准确度最高,这个问题提交工单反馈给高德,回复就是bug,哎就是不改,众所周知所谓“已经排期修改了”就是不改了。
所以如果要提高数据准确性和获取数量,代码中的maxcount最好设为70,但这样会浪费key的查询次数限额,个人做取舍吧。我设为70后得到的成都餐饮服务POI数据量18.3W
第二个坑,返回的POI点数不全!
例如我发现区域内某一个poi点位,在划定区域内不返回,但在另一个划定区域内有返回。这个点位都在这两个划定区域内,只不过两个划定区域起始结束经纬度不同。这个坑最大,查了半天最后发现是官方返回接口的问题。提工单咨询明确回复就是不给你全部数据,要全部数据别用这个接口,去找我们商务买。包括周边搜索这个接口测试一样的,都不会返回全。
所以这一块想拿到更多数据,技术上可行的方法只有多次用不同起始度的矩形划分抓取,得到多个导出结果最后合并去重,这样会浪费更多的key限额,比上一个key浪费都多。
您账号 下的工单有新回复。
多边形搜索API返回结果不准确
回复详情:
您好,由于考虑到数据安全问题,调用服务查询某类型的POI信息可能是无法返回全量数据的,搜索服务更推荐您输入关键字搜索进行精搜,若需要某类型的全量数据,可以提交商务工单咨询,给您带来不便,还请谅解~ 商务工单提交链接:高德控制台 有任何问题您都可以通过工单方式联系我们,感谢您的咨询与反馈,欢迎持续关注高德开放平台!工单完结可点击页面右上角的“关闭工单”,并对此次服务做出评价。
所以抓取高德、百度、腾讯地图最大的坑最后是官方不返回全部POI数据。要想获取全部的,我能想到的只有不同网格多次抓最后合并去重。不知道还有其他什么更好的高效省流方法。
不过如果不要求全量准确数据,损失三四成的数据也勉强够用了。