高德地图POI爬虫:爬取商户与景点数据,做LBS应用

引言

做LBS应用,POI数据是第一块砖。选址分析要看竞品密度,商圈研究要看业态分布,热力图要看点位聚合------这些分析的质量,直接取决于POI数据的覆盖度和准确度。

高德地图的POI接口看起来很简单:申请一个Key,传一个关键词和城市名,就能拿到商户和景点数据。但只要你试过"拉全量",问题会立刻暴露。直接传一个大范围再给关键词,返回结果按相关度截断,翻页翻到后面要么循环重复,要么数据缺得离谱。官方文档也写得很清楚:同请求参数翻页查询最多支持获取200条数据,搜索不支持返回全量数据。对于一个POI密集的城市,200条连一个商圈都覆盖不全。

更麻烦的是配额。2025年5月20日起,高德基础搜索服务的调用限制从"日配额"调整为"月配额",个人认证开发者月配额5000次,企业认证开发者50000次。按每次请求最多拿200条计算,5000次月配额的理论上限是100万条,但这是在"每次请求都满载"的理想情况下,实际采集中的探测请求、翻页请求、去重后的有效数据量都会让这个数字打折扣。

这意味着,全量POI采集不是"多翻几页"就能解决的问题,而是一套空间切分、溢出检测、接口选型、代理配合 的系统工程。本文将围绕这套工程链路展开,并说明站大爷隧道代理IP在多Key、高频请求场景中如何帮助分散IP层面的累积风险。

免责声明:本文仅限技术学习与交流。POI数据采集应遵守高德开放平台的服务条款及相关法律法规,仅将技术用于学习和研究目的,不得将数据用于商业转售或侵犯平台权益。

一、两条数据路径:官方API与前端接口

采集高德POI,有两条本质不同的路径。

1.1 官方API:合规、稳定,但有硬上限

高德Web服务API提供关键字搜索(/v3/place/text)、周边搜索(/v3/place/around)、多边形搜索(/v3/place/polygon)和ID查询四种机制。请求需要携带Key,返回JSON格式的结构化数据,字段包括POI名称、类型、经纬度、地址、电话等。

官方API的优势是合规、稳定、字段完整 ,适合大多数学习和研究场景。限制也很明确:单次搜索翻页最多200条,配额按月计算,超限后返回DAILY_QUERY_OVER_LIMIT等错误。

1.2 前端接口:数据更全,但需要逆向

高德网页端和App端使用的内部接口,在返回数据量上通常比公开API宽松。但这类接口携带动态生成的请求头,例如x-dc和bx-ua,其中bx-ua的生成逻辑被混淆,需要逆向JS还原;x-dc在少量请求时可能不是必须的,但服务端会校验其合法性。

此外,高频访问前端接口会触发JS Challenge(返回含混淆JavaScript的页面)或滑块验证。这类方案的逆向成本和维护成本都很高,只适合有逆向能力的团队做技术研究,不适合作为常规采集方案。

对于大多数开发者,官方API + 空间切分是更务实、更可持续的选择。本文后续的实现以官方API为主。

二、突破200条上限:网格切分与四叉树递归

官方API的200条上限不是"翻页"能绕过的------它是服务端的总量截断。唯一的解法是从物理层面缩小每次请求的空间范围,让每个子区域内的POI数量低于上限。

2.1 矩形网格切分

核心思路很朴素:把目标城市切成一个个小矩形,逐格调用多边形搜索接口,每个格子内的POI数量通常只有百级别,翻一两页就能拿全。

高德的/v3/place/polygon接口接收一组坐标点串,矩形是它的特例。整体流程四步:切 (按经纬度步长切分)、拉 (逐格请求,按页翻完)、并 (按POI ID去重合并)、存(写入SQLite或CSV)。

网格粒度直接决定采集效果。步长太大,密集区会溢出;步长太小,请求量爆炸。一组可参考的经验值:

场景 步长建议 原因
市中心密集区/重点商圈 0.005°~0.01° 单格密度大,小格子避免截断
一般城市建成区 0.01°~0.02° 覆盖与请求量均衡
郊区、县域 0.02°~0.05° 数据稀疏,格子可以大
城市级宏观粗筛 0.05°~0.1° 先定位高密度区块,再二次细分

1度纬度约111公里,1度经度的实际距离需要乘以cos(lat)修正。以北京(纬度约40°)为例,0.01度经度约0.85公里,0.01度纬度约1.1公里。

实际项目中推荐两阶段策略:先用粗网格(如0.05°)跑一遍,标记"装满"的格子;第二阶段只对高密度格子细分到0.005°重跑。这样既不会一上来就发起海量请求,又能保证密集区不丢数据。

2.2 四叉树递归:让网格"自动变焦"

固定步长的网格切分有一个问题:步长是预先设定的,无法根据实际密度自适应。四叉树递归解决了这个问题。

算法逻辑是检测---分裂---递归:请求当前矩形区域,如果翻到最大页数时数据仍然是满的(例如每页25条,第7页仍然满25条),说明该区域数据量可能超过了可返回上限,存在截断风险;此时将矩形等分为四个子矩形,递归执行同样的检测,直到每个子区域的数据量低于阈值或达到最小面积限制。

一个工程细节是空间裁剪 :通过shapely判断当前网格是否与城市轮廓相交,如果不相交(例如海面、荒山),直接跳过,节省大量无效请求和配额。另一个细节是初始粗切:先把城市切成100km级别的大块,再进入递归,降低递归栈深度,也方便断点续跑。

四叉树的效果可以这样理解:郊区用大网格快速扫过,市中心自动细分到50米级别的小格子,直到能把每个格子的数据完整拉下来。

三、API选型与请求封装

3.1 多边形搜索 vs 关键字搜索

全量采集应优先使用多边形搜索,而不是关键字搜索。原因有两个:关键字搜索按相关度排序后截断,覆盖不全;多边形搜索只按空间范围过滤,更适合"把区域内的POI全部拉出来"的场景。

如果要按类别采集,不要只依赖keywords,而应该使用types分类编码分段拉取。例如餐饮类传050000,购物类传060000,每个大类分别请求,才能拼出完整的业态数据。

3.2 请求封装

一个带重试和配额监控的请求封装:

复制代码
import requests, time, random

BASE = "https://restapi.amap.com/v3/place/polygon"

def fetch_polygon(polygon, key, types=None, page=1, offset=25,
                  proxies=None, retry=3):
    params = {
        "key": key,
        "polygon": polygon,   # "lon1,lat1|lon2,lat2|lon3,lat3|lon4,lat4"
        "offset": offset,
        "page": page,
        "extensions": "base",
    }
    if types:
        params["types"] = types

    for attempt in range(retry):
        try:
            resp = requests.get(
                BASE, params=params, proxies=proxies, timeout=15
            )
            data = resp.json()
            if data.get("status") == "1":
                return data
            # 配额/频率类错误,等待后重试
            infocode = data.get("infocode", "")
            if infocode in ("10003", "10004", "10021", "10029"):
                time.sleep(3 * (attempt + 1))
                continue
            return data
        except Exception:
            time.sleep(2 * (attempt + 1))
    return None

3.3 断点续跑与去重

每个网格的完成状态需要持久化,避免中断后从头再来。用SQLite记录"已完成的网格ID"和"已采集的POI ID":

复制代码
CREATE TABLE grids (
    grid_id TEXT PRIMARY KEY,
    status TEXT,          -- done / partial
    finished_at TEXT
);

CREATE TABLE pois (
    poi_id TEXT PRIMARY KEY,
    name TEXT, type TEXT,
    lon REAL, lat REAL,
    address TEXT, grid_id TEXT
);

插入POI时用INSERT OR IGNORE,天然去重。网格完成后更新grids表。重启时跳过status='done'的网格,只补跑未完成的格子。

四、反爬的真实重点:不是IP,是JS挑战和Key管理

4.1 IP层面的封锁并非主要矛盾

有实践者指出,高德对IP地址本身的反爬机制并不算严格。真正的障碍是JS Challenge和滑块验证------当请求模式异常时,服务端返回的不再是JSON,而是含混淆JS的HTML页面或滑块验证页。

这意味着,单纯"换IP"并不能解决所有问题。如果请求的参数、频率、会话特征本身有问题,换多少个IP都会触发挑战。

4.2 Key管理是核心风控维度

高德的配额和风控是以Key为维度的。免费Key有日调用量上限和QPS限制。更关键的是,如果代理IP池与Key没有绑定关系,可能因账号关联风控被降级。

实践中需要做到:每个Key绑定固定的出口IP或IP段,多Key轮询时确保同一个Key的请求来源相对稳定,避免"一个Key今天用北京IP、明天用广州IP"造成异常。如果使用代理,应该为每个Key配置独立的代理出口,而不是所有Key共享一个轮换池。

4.3 站大爷隧道代理的配合方式

在这个风控框架下,站大爷隧道代理IP 的作用是在Key绑定的前提下,为每个Key提供稳定的出口通道,并在需要多Key轮询时隔离不同Key的请求来源:

  • Key与隧道入口绑定 :不同Key使用不同的隧道入口(或不同的period轮换周期),让每个Key的请求来源保持一致性,降低账号关联风控的风险。

  • 分散单IP的累积请求量:即使高德对IP本身不敏感,单IP在长时间运行中累积的请求量仍然可能触发行为层面的挑战。隧道代理的自动轮换可以把请求分散到不同出口,减少单IP的"曝光度"。

  • 覆盖广、可用性高:站大爷覆盖全国99%地域,连接成功率在长时间压力测试中稳定在99%以上,适合需要持续运行的网格采集任务。

  • 地域定向:如果采集的POI需要与特定城市对应,可以选择对应地域的出口IP,让请求来源与数据地域保持一致。

集成方式很简单:

复制代码
PROXIES = {
    "http": "http://用户名:密码@tps.zdaye.com:8080",
    "https": "http://用户名:密码@tps.zdaye.com:8080",
}

需要强调的是:代理解决的是"请求来源的分散和稳定",它不替代Key管理和频率控制。如果Key本身的配额耗尽或触发风控,代理换再多IP也无济于事。

五、从POI数据到LBS应用

采集只是起点,清洗和可视化才是POI数据产生价值的地方。

5.1 数据清洗

原始POI数据需要几项基础清洗:按POI ID去重 (不同网格可能返回同一个POI);过滤类型 (只保留目标业态,如餐饮的050000下各子类);坐标系确认 (高德返回的是GCJ-02坐标,与其他坐标系混用时需要转换);地址标准化(同一商场内的不同商户可能有多条地址表述)。

5.2 选址分析:竞品密度与配套评估

POI数据最直接的应用是商圈选址分析。以某个候选点位为中心,统计周围一定半径内的竞品数量、交通设施数量、住宅小区数量,计算"竞品密度"和"配套完整度"。有开源项目已经实现了完整的单地址选址分析:竞品搜索、配套评估、交通态势、热力图可视化,多地址对比还支持雷达图和柱状图排名。

5.3 热力图与空间聚合

将POI按经纬度聚合到网格中,用颜色深浅表示密度,生成交互式热力图。高德POI数据本身就可以驱动这种可视化------把清洗后的点位数据叠加到地图底图上,用folium或pyecharts生成HTML即可。

5.4 业态结构分析

按types字段统计各一级分类的POI数量和占比,可以快速判断一个区域的业态结构:餐饮主导、零售主导还是服务业主导。进一步计算"餐饮/零售比""服务/人口比"等衍生指标,可以为商业定位提供参考。

六、常见问题与避坑指南

翻页翻到一半开始重复或数据变少。 这是相关度截断的典型表现。不要试图在"大范围+翻页"的模式下硬拉,切换到网格切分或四叉树递归,从空间上缩小请求范围。

某个网格返回的数据明显偏少。 检查是否只用了keywords而遗漏了types分类编码。名称不包含关键词的POI(如"张亮麻辣烫"可能不命中"餐厅"关键词)会被过滤掉,按types分段拉取才能覆盖全。

配额消耗过快。 两阶段策略是有效的控制手段:粗网格定位高密度区,只对"装满"的格子做细分。同时用shapely裁剪掉城市轮廓外的无效网格,避免把配额浪费在海面和荒山上。

触发JS Challenge或滑块。 降低请求频率,增加随机延迟,检查Key与IP的绑定关系是否稳定。如果挑战仍然频繁出现,说明请求模式本身需要调整,而不是单纯增加代理。

多Key轮询时部分Key被降级。 检查每个Key是否绑定了一致的出口IP。代理池与Key混用、所有Key共享同一个轮换池,是账号关联风控的常见诱因。

七、总结

高德POI爬虫的核心矛盾,不是"能不能发请求",而是"如何在配额和返回上限的约束下拿到可用的全量数据":

环节 关键做法
接口选型 优先官方API多边形搜索,避免前端接口逆向的高维护成本
突破200条上限 矩形网格切分 / 四叉树递归,从空间上缩小请求范围
网格粒度 两阶段策略:粗网格定位,高密度区细分
反爬重点 不是IP封锁,而是JS Challenge、滑块和Key级风控
代理配合 站大爷隧道代理按Key绑定出口,分散累积请求,保持来源稳定
数据清洗 POI ID去重、类型过滤、坐标系确认
LBS应用 竞品密度、配套评估、热力图、业态结构分析

POI数据的价值不在"采了多少条",而在"覆盖得对不对"。一个经过网格切分、四叉树补全、按类型分段拉取的数据集,比一个"大范围翻页翻到截断"的数据集,在选址分析和热力图上得出的结论可能完全不同。站大爷隧道代理在这个链路中承担的是"让请求来源稳定且分散"的角色------它不解决接口上限问题,但可以帮助多Key、高频采集在IP层面保持平稳,减少因来源异常触发的挑战和风控。

最后需要提醒的是:高德POI数据采集应遵守平台的服务条款和配额规则,控制请求频率,不将数据用于商业转售。技术让数据触手可及,合规边界让项目可持续。

相关推荐
weixin199701080162 小时前
官方授权通道和灰色爬虫,做数据服务该选哪条路(附 python 源码)
c++·爬虫·python
正飞信息技术1 天前
llms.txt部署指南:给AI爬虫一张清晰的网站地图
人工智能·爬虫
NeoGressAI外贸数字化3 天前
外贸独立站robots.txt怎么写:6个AI爬虫放行规则
人工智能·爬虫
OnlineProxy3 天前
移动应用数据爬取:通过模拟器与流量嗅探对 API 进行逆向工程
爬虫·python·httpx
傻啦嘿哟3 天前
分布式爬虫架构:Redis+Scrapy打造企业级爬虫系统
分布式·爬虫·架构
梯度下降者4 天前
零基础入门Python 爬虫
爬虫·python·数据分析·入门指南·webcrawler
2601_967979124 天前
0 到 1 学爬虫:用 Python 爬取网页数据指南一(附实战案例)
爬虫·python·实战案例·html解析·网页数据
姚瑞南4 天前
【PRD】招投标商机雷达爬虫系统
人工智能·经验分享·笔记·爬虫·产品运营·产品经理
嫂子的姐夫5 天前
055-wasm:爬虫练习网spa15(token 纯算还原)
爬虫·wasm