拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python爬虫进阶实战:高德/百度地图API批量采集周边POI商户数据

Python爬虫进阶实战:高德/百度地图API批量采集周边POI商户数据

在做线下门店选址、商圈分析、竞品调研这类业务场景时,周边商户的POI数据是核心参考依据。手动逐个搜索不仅效率极低,也很难做到全量覆盖。通过高德、百度地图开放平台的官方API,结合Python实现批量自动化采集,可以高效获取标准化的POI数据,大幅提升数据获取效率。

本文基于高德和百度地图的公开API,完整实现周边商户POI的批量采集,覆盖网格切分突破数量限制、分页拉取、自动去重、数据清洗、Excel导出全流程,同时整理了实际开发中常见的坑点与解决方案。

一、前期准备与整体方案

1.1 技术栈说明

只用基础库即可完成,无需复杂依赖:

  • requests:负责API接口的HTTP请求
  • pandas:负责结构化数据处理、去重与导出
  • math:负责经纬度网格切分计算
  • time:负责请求间隔控制与限流规避
  • json:负责接口返回数据解析

1.2 API密钥申请

两个平台的API都需要先申请密钥,步骤基本一致:

  1. 进入对应地图开放平台官网,注册开发者账号
  2. 创建应用,选择「Web服务」类型的密钥
  3. 开通「周边搜索」相关的接口权限
  4. 复制生成的密钥填入代码配置项

两个平台都有免费调用额度,个人学习和小规模使用基本足够,大批量采集建议购买官方额度,避免触发限流。

1.3 整体实现流程

采集参数配置

目标区域网格切分

单网格分页调用API

数据解析与字段提取

跨网格数据去重

数据清洗与格式标准化

Excel文件导出

二、核心原理与接口说明

2.1 为什么要做网格切分

这是批量采集中最关键的一个点。地图API的周边搜索接口都有返回数量上限:高德单次周边搜索最多返回1000条结果,百度最多返回760条。如果直接在大范围内调用,核心区域的大量POI会被截断,无法获取全量数据。

最开始做的时候图省事,直接用5公里大半径调用,结果发现核心商圈的店铺缺了近一半,后来才知道有返回上限,踩了不小的坑。解决方案就是把目标区域按经纬度切分成若干个小网格,每个网格的半径控制在返回结果不超过上限的范围内,逐个网格采集后再合并去重,就能实现大范围的全量POI获取。

2.2 高德地图周边搜索API

接口地址:[https://restapi.amap.com/v3/place/around](https://restapi.amap.com/v3/place/around)
核心请求参数:

  • key:开发者密钥
  • location:中心点经纬度,格式为「经度,纬度」
  • keywords:搜索关键词,如“餐饮”“酒店”
  • radius:搜索半径,单位米,最大50000
  • offset:单页返回数量,最大25
  • page:页码
  • extensions:返回结果控制,base返回基本信息,all返回详细信息

2.3 百度地图周边搜索API

接口地址:[https://api.map.baidu.com/place/v2/search](https://api.map.baidu.com/place/v2/search)
核心请求参数:

  • ak:开发者密钥
  • query:搜索关键词
  • location:中心点经纬度,格式为「纬度,经度」
  • radius:搜索半径,单位米
  • page_size:单页返回数量,最大20
  • page_num:页码
  • output:返回格式,选json

这里踩过第二个坑:两个平台的经纬度顺序是反的,高德是经度在前,百度是纬度在前,一开始没注意的时候返回的结果全是错位的,调了半天才发现是参数顺序的问题。

三、分步代码实现

3.1 配置项与网格切分工具

先定义基础配置和通用工具函数,核心是经纬度网格切分的实现。

importrequestsimportpandasaspdimportmathimporttime# ===================== 配置项 =====================# 高德配置AMAP_KEY="你的高德开发者Key"# 百度配置BAIDU_AK="你的百度开发者AK"# 采集参数KEYWORD="便利店"# 目标区域:[西南经度, 西南纬度, 东北经度, 东北纬度]BOUNDS=[116.30,39.90,116.50,40.05]# 示例:北京部分区域# 网格步长,单位度,数值越小网格越密,数据越全但速度越慢GRID_STEP=0.02# 单网格搜索半径,单位米,配合步长设置RADIUS=1500# 请求间隔,单位秒SLEEP_TIME=0.2

网格切分函数,根据输入的区域边界和步长,生成所有网格的中心点经纬度:

defgenerate_grid_points(bounds,step):""" 根据区域边界生成网格中心点列表 :param bounds: [西南经度, 西南纬度, 东北经度, 东北纬度] :param step: 网格步长,单位度 :return: 中心点经纬度列表 [(lon, lat), ...] """min_lon,min_lat,max_lon,max_lat=bounds points=[]lon_steps=math.ceil((max_lon-min_lon)/step)lat_steps=math.ceil((max_lat-min_lat)/step)foriinrange(lon_steps+1):forjinrange(lat_steps+1):lon=min_lon+i*step lat=min_lat+j*stepiflon<=max_lonandlat<=max_lat:points.append((round(lon,6),round(lat,6)))print(f"共生成{len(points)}个网格中心点")returnpoints

3.2 高德地图POI采集实现

封装单页采集函数,再实现批量分页与网格遍历:

deffetch_amap_poi(lon,lat,keyword,radius,page):"""采集单页高德POI数据"""url="[https://restapi.amap.com/v3/place/around](https://restapi.amap.com/v3/place/around)"params={"key":AMAP_KEY,"location":f"{lon},{lat}","keywords":keyword,"radius":radius,"offset":25,"page":page,"extensions":"base"}try:resp=requests.get(url,params=params,timeout=10)resp.raise_for_status()data=resp.json()ifdata.get("status")!="1":print(f"高德接口报错:{data.get('info')}")return[]returndata.get("pois",[])exceptExceptionase:print(f"请求失败:{str(e)}")return[]defcollect_amap_all(keyword,bounds,step,radius):"""高德全量POI采集:网格切分+分页拉取"""grid_points=generate_grid_points(bounds,step)all_pois=[]foridx,(lon,lat)inenumerate(grid_points):print(f"正在采集第{idx+1}/{len(grid_points)}个网格:{lon},{lat}")page=1whileTrue:pois=fetch_amap_poi(lon,lat,keyword,radius,page)ifnotpois:breakall_pois.extend(pois)# 达到单页最大数量时继续翻页,不足则停止iflen(pois)<25:breakpage+=1time.sleep(SLEEP_TIME)time.sleep(SLEEP_TIME)print(f"原始采集完成,共获取{len(all_pois)}条数据")returnall_pois

3.3 百度地图POI采集实现

百度的逻辑和高德基本一致,重点注意参数名和经纬度顺序的区别:

deffetch_baidu_poi(lon,lat,keyword,radius,page):"""采集单页百度POI数据"""url="[https://api.map.baidu.com/place/v2/search](https://api.map.baidu.com/place/v2/search)"params={"ak":BAIDU_AK,"query":keyword,"location":f"{lat},{lon}",# 百度是纬度在前"radius":radius,"page_size":20,"page_num":page,"output":"json"}try:resp=requests.get(url,params=params,timeout=10)resp.raise_for_status()data=resp.json()ifdata.get("status")!=0:print(f"百度接口报错:{data.get('message')}")return[]returndata.get("results",[])exceptExceptionase:print(f"请求失败:{str(e)}")return[]defcollect_baidu_all(keyword,bounds,step,radius):"""百度全量POI采集:网格切分+分页拉取"""grid_points=generate_grid_points(bounds,step)all_pois=[]foridx,(lon,lat)inenumerate(grid_points):print(f"正在采集第{idx+1}/{len(grid_points)}个网格:{lon},{lat}")page=0whileTrue:pois=fetch_baidu_poi(lon,lat,keyword,radius,page)ifnotpois:breakall_pois.extend(pois)iflen(pois)<20:breakpage+=1time.sleep(SLEEP_TIME)time.sleep(SLEEP_TIME)print(f"原始采集完成,共获取{len(all_pois)}条数据")returnall_pois

3.4 数据去重与清洗

网格重叠会产生大量重复数据,需要根据POI唯一ID去重,再做字段标准化:

defclean_poi_data(raw_pois,platform="amap"):"""POI数据去重与字段清洗"""ifnotraw_pois:returnpd.DataFrame()cleaned=[]forpoiinraw_pois:ifplatform=="amap":location=poi.get("location","").split(",")item={"POI_ID":poi.get("id"),"名称":poi.get("name"),"类型":poi.get("type"),"地址":poi.get("address"),"经度":location[0]iflocationelse"","纬度":location[1]iflocationelse"","电话":poi.get("tel"),"省份":poi.get("pname"),"城市":poi.get("cityname"),"区县":poi.get("adname")}elifplatform=="baidu":location=poi.get("location",{})item={"POI_ID":poi.get("uid"),"名称":poi.get("name"),"类型":poi.get("category"),"地址":poi.get("address"),"经度":location.get("lng"),"纬度":location.get("lat"),"电话":poi.get("telephone"),"省份":poi.get("province"),"城市":poi.get("city"),"区县":poi.get("area")}cleaned.append(item)df=pd.DataFrame(cleaned)# 根据官方ID去重,比按名称去重准确得多before_count=len(df)df.drop_duplicates(subset="POI_ID",keep="first",inplace=True)after_count=len(df)print(f"去重完成:移除{before_count-after_count}条重复数据,剩余{after_count}条")df.reset_index(drop=True,inplace=True)returndf

3.5 导出Excel

最后把清洗好的数据导出为Excel,方便后续分析使用:

defexport_to_excel(df,keyword,platform):"""导出为Excel文件"""filename=f"{platform}_POI_{keyword}.xlsx"df.to_excel(filename,index=False,sheet_name="POI明细")print(f"数据已导出至:{filename}")

主函数调用示例:

if__name__=="__main__":# 高德采集示例raw_data=collect_amap_all(KEYWORD,BOUNDS,GRID_STEP,RADIUS)clean_df=clean_poi_data(raw_data,platform="amap")export_to_excel(clean_df,KEYWORD,"高德")# 百度采集示例# raw_data = collect_baidu_all(KEYWORD, BOUNDS, GRID_STEP, RADIUS)# clean_df = clean_poi_data(raw_data, platform="baidu")# export_to_excel(clean_df, KEYWORD, "百度")

四、常见问题与避坑指南

4.1 接口返回权限错误

  • 检查密钥是否正确,有没有复制多余空格
  • 确认应用类型是否正确,Web服务API不能用浏览器端的密钥
  • 查看对应接口是否已经开通权限,部分高级接口需要单独申请
  • 检查是否超出当日调用配额,超出后会返回报错

4.2 返回结果数量明显偏少

  • 检查网格步长和搜索半径是否匹配,步长太大导致区域覆盖不全
  • 确认关键词是否准确,部分分类关键词需要用平台的标准分类词
  • 查看是否已经翻到最大页数,高德最多返回1000条,百度最多760条,超出的话必须缩小网格
  • 部分偏远区域本身POI数量少,属于正常情况

4.3 坐标偏移问题

两个平台的坐标体系不一样:

  • 高德使用GCJ-02(火星坐标系)
  • 百度使用BD-09(百度坐标系)

如果需要统一坐标,需要做坐标转换。两个平台都提供了官方坐标转换API,也可以自己实现转换算法,注意不要直接用WGS84的经纬度去调用,会出现几百米的偏移。

4.4 触发限流与封禁

  • 严格控制请求频率,不要短时间内大量请求
  • 免费额度用完后会返回超限错误,等待次日重置或者购买额度
  • 不建议使用多线程并发请求,很容易触发风控,导致密钥临时封禁
  • 大批量采集建议使用多个密钥轮询,或者拉长请求间隔

4.5 数据重复或遗漏

  • 网格步长和搜索半径要配合设置,半径要略大于网格的对角线,避免出现缝隙
  • 重叠区域产生的重复数据通过POI_ID去重即可,不要用名称去重,容易误删同名店铺
  • 边界区域建议适当扩大采集范围,再按目标区域裁剪,避免边缘数据遗漏

五、总结与扩展思路

本文基于高德和百度地图的官方API,实现了大范围POI数据的批量采集,通过网格切分的方式解决了单接口返回数量上限的问题,完整覆盖从参数配置、数据拉取、去重清洗到导出的全流程。

如果需要进一步扩展能力,可以尝试这几个方向:

  1. 多关键词批量采集,一次性获取多个品类的POI数据
  2. 加入坐标转换功能,统一两个平台的数据坐标体系
  3. 接入数据库存储,实现增量采集与定期更新
  4. 结合可视化库,生成POI分布热力图
  5. 扩展多边形区域采集,适配不规则的商圈或行政区边界
返回列表