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

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)

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

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

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

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

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

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

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

尊敬的开发者:

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

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

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

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

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

 

 

 

 

 

 

 

  • 7
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
成都的所有种类POI有17类:美食 中餐厅、外国餐厅、小吃快餐店、蛋糕甜品店、咖啡厅、茶座、酒吧 酒店 星级酒店、快捷酒店、公寓式酒店 购物 购物中心、百货商场、超市、便利店、家居建材、家电数码、商铺、集市 生活服务 通讯营业厅、邮局、物流公司、售票处、洗衣店、图文快印店、照相馆、房产中介机构、公用事业、维修、家政服务、殡葬服务、彩票销售、宠物服务、报刊亭、公共厕所 丽人 美容、美发、美甲、美体 旅游景 公园、动物园、植物园、游乐园、博物馆、水族馆、海滨浴场、文物古迹、教堂、风景区 休闲娱乐 度假村、农家院、电影院、KTV、剧院、歌舞厅、网吧、游戏场所、洗浴按摩、休闲广场 运动健身 体育场馆、极限运动场所、健身中心 教育培训 高等院校、中学、小学、幼儿园、成人教育、亲子教育、特殊教育学校、留学中介机构、科研机构、培训机构、图书馆、科技馆 文化传媒 新闻出版、广播电视、艺术团体、美术馆、展览馆、文化宫 医疗 综合医院、专科医院、诊所、药店、体检机构、疗养院、急救中心、疾控中心 汽车服务 汽车销售、汽车维修、汽车美容、汽车配件、汽车租赁、汽车检测场 交通设施 飞机场、火车站、地铁站、地铁线路、长途汽车站、公交车站、公交线路、港口、停车场、加油加气站、服务区、收费站、桥、充电站、路侧停车位 金融 银行、ATM、信用社、投资理财、典当行 房地产 写字楼、住宅区、宿舍 公司企业 公司、园区、农林园艺、厂矿 政府机构 中央机构、各级政府、行政单位、公检法机构、涉外机构、党派团体、福利机构、政治教育机构 出入口 高速公路出口、高速公路入口、机场出口、机场入口、车站出口、车站入口、门(备注:建筑物和建筑物群的门)、停车场出入口 自然地物 岛屿、山峰、水系
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逸雨清风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值