Python获取高德POI兴趣点数据 以及高德的两个坑 示例抓取成都市餐饮服务大类

POI数据,英文全称Point of Intersesting,中文的意思是兴趣点,指的是在地图上有意义的点:比如商店、酒吧、加油站、医院、车站等。POI数据能够赋能时空行为、城市规划、地理信息等研究,因此获取准而全的POI数据是开展科研工作的基础。

高德POI大类有汽车服务 汽车销售 汽车维修 摩托车服务 餐饮服务 购物服务 生活服务 体育休闲服务 医疗保健服务 住宿服务 风景名胜 商务住宅 政府机构及社会团体 科教文化服务 交通设施服务 金融保险服务 公司企业 道路附属设施 地名地址信息 公共设施 事件活动 室内设施 通行设施。

首先要在高德开放平台申请key,每天各个接口查询都有限额,一般来讲够用。

本文以抓取成都市的餐饮服务业为例讲述。

这里调用高德的多边形接口来选定区域。

Python获取高德POI兴趣点数据 以及高德的两个坑 示例抓取成都市餐饮服务大类_第1张图片

需要注意的大前提是高德官方文档的这句话:“另外,无论您指定多少个type,每次请求最多返回1000个POI信息,若场景需要获取更可能多的POI;建议您不要在type之中指定过多的类别,而是分多次请求从而得到更加准确的结果。

也就是我们每次请求一个区域内最多返回1000条数据,所以为了得到全部数据,当返回数据大于1000时,必须把该区域拆分为多个区域再次查询,如果拆分的区域里仍然大于1000,则继续拆分,就是一个递归的过程,另外实际上最大值实测用800条更稳妥。这一块递归拆分的算法是全过程技术难度最大的算法,我是搞不定了,还好网上有大佬现成的算法拿来用就行。

多边形搜索POI这个接口关键点就是传入好网格经纬度参数,传入矩形左上角和右下角经纬度即可。

Python获取高德POI兴趣点数据 以及高德的两个坑 示例抓取成都市餐饮服务大类_第2张图片

Python获取高德POI兴趣点数据 以及高德的两个坑 示例抓取成都市餐饮服务大类_第3张图片 

首先我们要获取成都市的区域边界,这也是一个多边形经纬度,高德官方也有这个接口。

https://restapi.amap.com/v3/config/district?keywords=%E6%88%90%E9%83%BD%E5%B8%82&subdistrict=0&extensions=all&key=<你的key>

Python获取高德POI兴趣点数据 以及高德的两个坑 示例抓取成都市餐饮服务大类_第4张图片

把得到的这些坐标保存到桌面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。

Python获取高德POI兴趣点数据 以及高德的两个坑 示例抓取成都市餐饮服务大类_第5张图片

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)

这样大概就会得到一份15W条左右的成都餐饮服务POI点位数据,我截图里是另外处理过有18.5W条,据其他渠道了解全部数据大概是25W条。也就是说这样会大量缺失POI点位!

根据测试网上其他软件和数据,得到的成都餐饮POI大概都是15W,所以下面再讲高德的两个坑和改进思路。

第一个坑,返回的count是不正确的,实测count越大准确率越低,多次测试发现count最大值为70时候count准确度最高,这个问题提交工单反馈给高德,回复就是bug,哎就是不改,众所周知所谓“已经排期修改了”就是不改了。

Python获取高德POI兴趣点数据 以及高德的两个坑 示例抓取成都市餐饮服务大类_第6张图片

所以如果要提高数据准确性和获取数量,代码中的maxcount最好设为70,但这样会浪费key的查询次数限额,个人做取舍吧。我设为70后得到的成都餐饮服务POI数据量18.3W

第二个坑,返回的POI点数不全!

例如我发现区域内某一个poi点位,在划定区域内不返回,但在另一个划定区域内有返回。这个点位都在这两个划定区域内,只不过两个划定区域起始结束经纬度不同。这个坑最大,查了半天最后发现是官方返回接口的问题。提工单咨询明确回复就是不给你全部数据,要全部数据别用这个接口,去找我们商务买。包括周边搜索这个接口测试一样的,都不会返回全。

所以这一块想拿到更多数据,技术上可行的方法只有多次用不同起始度的矩形划分抓取,得到多个导出结果最后合并去重,这样会浪费更多的key限额,比上一个key浪费都多。

尊敬的开发者:

您账号 下的工单有新回复。

多边形搜索API返回结果不准确

回复详情:
您好,由于考虑到数据安全问题,调用服务查询某类型的POI信息可能是无法返回全量数据的,搜索服务更推荐您输入关键字搜索进行精搜,若需要某类型的全量数据,可以提交商务工单咨询,给您带来不便,还请谅解~ 商务工单提交链接:高德控制台 有任何问题您都可以通过工单方式联系我们,感谢您的咨询与反馈,欢迎持续关注高德开放平台!工单完结可点击页面右上角的“关闭工单”,并对此次服务做出评价。

Python获取高德POI兴趣点数据 以及高德的两个坑 示例抓取成都市餐饮服务大类_第7张图片

所以抓取高德、百度、腾讯地图最大的坑最后是官方不返回全部POI数据。要想获取全部的,我能想到的只有不同网格多次抓最后合并去重。不知道还有其他什么更好的高效省流方法。

不过如果不要求全量准确数据,损失三四成的数据也勉强够用了。

 

 

 

 

 

 

 

你可能感兴趣的:(python,大数据)