ARTICLE DETAIL

资讯详情

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

高德地图爬虫实战:POI采集、坐标系偏移与并发控制全解析

高德地图爬虫实战:POI采集、坐标系偏移与并发控制全解析 先说一个可能和你第一印象不太一样的结论很多人以为的“高德地图爬虫”是写个脚本去高德官网页面把某个城市的POI兴趣点数据全部抓下来存到Excel里就完事。但真正动手之后你会发现这个需求背后牵扯到的东西远比“发个HTTP请求”要大坐标系偏移、API配额、签名算法、瓦片编号、并发控制、IP代理池……我最早接触这块是因为要做某个城市的餐饮POI分布分析需要把“美食”分类下的商户名称、地址、经纬度、评分全部拿下来。当时我也天真地以为用requests循环翻页就行结果第一版脚本跑了不到三分钟就触发限流数据还出现了好几公里的坐标偏位。后来把整条链路重新梳理了一遍才算把高德地图爬虫这件事真正吃透。这篇文章就围绕高德地图数据采集的几条主流路线展开官方API怎么用、Web端接口怎么抓、瓦片地图怎么下载、并发和代理怎么设计以及我在实战里踩过的那些坑。适合正在做地图数据采集、LBS数据分析或者离线地图应用的开发者参考。1. 先搞清楚一件事高德地图爬虫到底在爬什么1.1 数据源拆解POI、路径、路况、瓦片各是什么高德地图背后的数据类型比大多数人想象中丰富得多。你不能笼统地说“我要爬高德地图”因为不同的数据来源对应完全不同的采集方案。第一类是POI数据也就是具体的地点信息比如店铺名、地址、电话、经纬度、所属分类、评分、评论数。这类数据是LBS分析和商业选址最常用的也是绝大多数人说的“高德爬虫”真正想要的东西。第二类是路径规划数据包括两点之间的驾车/步行/骑行路线、预计耗时、距离、途经点。这类数据适合做导航对比、路况分析但单次请求消耗的配额比较大一般项目很少大规模爬取。第三类是路况与实时交通数据比如某个路段的拥堵指数、平均车速。这类数据通常是时间序列需要持续监控爬下来之后能用来做红绿灯算法研究、拥堵预测之类的事情。第四类是瓦片地图数据就是把地图切成一张张小图按层级和行列号组织常用在离线地图、自建GIS系统、游戏背景底图等场景。瓦片爬虫和前面几类的技术路线完全不同它爬的是图片不涉及JSON解析但涉及拼接和坐标投影。搞清楚这一点之后你才能判断自己到底该走API路线、Web端爬虫路线还是瓦片下载路线。我见过太多人上来就写爬虫去怼网页版页面结果官方API明明就够用。1.2 为什么说它和普通网站爬虫不是一个难度等级普通网站爬虫的套路是很固定的分析页面结构找到数据接口发送请求解析JSON或HTML存入数据库。但高德地图爬虫在这套流程上加了三道保险。第一道保险是坐标偏移。高德使用的GCJ-02坐标系是由国家测绘局发布的加密坐标系WGS-84GPS原始坐标转到GCJ-02会有一个偏移反过来也一样。如果你拿爬到的坐标直接叠加在Google Earth或自己基于WGS-84的地图底图上会发现所有点位都偏了几百米。这个问题不解决后面的数据分析和可视化全部白搭。第二道保险是请求签名。高德网页版的API接口并不是随便拿requests就能直接调的请求里带着复杂的签名参数比如热词里反复出现的hermes。这个参数是前端JS加密生成的服务端校验通过才返回数据。第三道保险是配额限制。无论是官方API还是网页版接口都有限流机制。官方API按QPS每秒请求数限制网页版接口更敏感请求频率稍微高了就直接封IP或者返回验证码。所以高德地图爬虫从来不是一个requests就能搞定的小脚本它是一个包含数据获取、坐标转换、频率控制、反反爬、存储清洗的完整小工程。2. 动手前必须先算清的合规账和坐标问题2.1 爬虫与API的灰色边界谈技术之前先把边界说清楚。高德官方提供了一套非常完整的Web服务API包括POI检索、路径规划、地理编码、逆地理编码等。如果你想做地图数据采集第一优先永远是申请官方API key按官方文档来调。这套方案最稳定、数据质量最高、字段最全。但官方API有两个硬伤这也是大家转头去研究Web端爬虫的原因。第一个硬伤是配额。个人开发者默认的POI检索并发限制很低尤其是关键词检索一天的总量上限可能只够跑一个中小城市。热词里那句“高德地图api收费坑人”说的也是这个——当你发现免费配额不够用想要加量的时候费用会一下子拉高。有段时间高德调整了服务配额策略很多批量采集的项目直接断粮逼得人不得不想别的办法。第二个硬伤是字段受限。官方API返回的数据核心字段齐全但像评论数、营业时间、评分详情这类运营字段API不一定给你完整返回。而网页版接口里能看到更丰富的数据所以才会有人冒险去研究Web端的签名机制。我的态度是能走API就走APIAPI配额不够再考虑网页端研究网页端只用于补充API拿不到的字段。至于瓦片下载它本身是高德允许离线使用的方向只是不要把在线瓦片抓下来做二次分发就好。严格来说爬虫是否合规取决于用途、规模和频率这个自己在动手前要先有判断。2.2 GCJ-02坐标系所有高德坐标都绕不开的偏移GCJ-02是高德所有坐标数据的默认基准。你从任何渠道拿到的经纬度只要来自高德都是基于GCJ-02的。GPS设备拿到的原始坐标是WGS-84这两个坐标系之间不是简单的平移而是一种非线性偏移偏移量随地理位置变化通常在几十米到几百米之间。如果你只是在高德地图上标注点位那不需要做任何转换直接用就行。但如果你要把高德爬到的数据放到自建的Leaflet底图上或者和WGS-84的GPS轨迹做空间分析那就必须做坐标转换。网上流传的WGS-84转GCJ-02算法是公开的Python实现也很多核心就是那套带三角函数的偏移公式。我自己在实际项目里的做法是爬取阶段不转坐标统一保留GCJ-02入库之后做一层清洗把经纬度字段同时存一份WGS-84版本展示阶段再按底图坐标系选字段。这样最灵活不会因为转换误差导致原始数据丢失。还有个很容易被忽略的点热词里提到的“苹果手机位置错误”很多其实就是坐标系问题。iPhone上报的GPS坐标是WGS-84直接叠加在高德地图上就会偏移正确做法是先转成GCJ-02再展示。这和高德爬虫里坐标清洗是同一套原理。2.3 API配额与计费热词里的“收费坑人”到底坑在哪“高德地图api收费坑人”这个热词我的理解是坑的不是收费本身而是额度调整带来的不确定性和文档不透明。高德的个人开发者认证之后POI检索类接口会提供一定额度的免费调用量。但这里有几个隐藏规则容易踩第一并发配额和每日配额是分开算的。QPS限制了你每秒能发多少请求日配额限制了你一天能发多少请求。两个都卡得很死。第二不同接口的配额不共享。POI检索的配额和地理编码的配额是各算各的一旦某个接口消耗完了另一个还有余量也没法挪用。第三文档里写的配额数字和实际返回的报错有时候对不上。你可能明明没超配额但接口就是返回“CUQPS_LIMIT”之类的错误码过一会儿又好了这就是服务端的动态限流。我建议的做法是写代码之前先去高德开放平台的控制台把自己账号当前各接口的实际配额截图存下来然后按最低值来设计并发。宁可慢一点稳一点也别把配额跑穿后等第二天恢复。3. 官方API路线稳定获取POI数据的完整实践3.1 key申请与权限配置官方API的第一步是注册高德开放平台账号创建应用并申请Web服务API的key。创建应用的时候要选“Web服务”平台会让你添加服务类型然后把需要的接口勾上。这里有个细节POI检索在权限列表里叫“搜索POI”地理编码和逆地理编码是另一个服务需要分别开通。很多人只勾了一个结果调另一个接口的时候报“INVALID_USER_KEY”排查半天才发现是权限没开全。Key申请完之后建议在代码里把key放到环境变量或者配置文件中不要硬编码。我吃过一次亏代码push到公开仓库后key被扫描机器人捡走了第二天配额就被刷空只能重置key。从那之后我养成了一个习惯所有密钥都走环境变量管理。3.2 关键词检索与周边检索的参数细节高德POI检索的核心接口是/v3/place/text也就是关键词检索。它的基本逻辑是你给一个关键词和一个城市它返回匹配的POI列表。举个例子要把北京市所有“火锅”相关的POI抓下来伪代码是这样的import requests params { key: 你的key, keywords: 火锅, city: 北京, citylimit: true, offset: 25, page: 1, extensions: all } resp requests.get( https://restapi.amap.com/v3/place/text, paramsparams, timeout10 ) data resp.json()这里几个参数我重点说明一下。citylimit必须设置成true。如果不设高德会默认在全国范围内搜索返回一堆外地同名结果数据噪音非常大。extensionsall表示返回全部扩展字段包括评分、营业时间、电话等如果只要基础字段用base可以省配额因为all的配额消耗通常是base的好几倍。offset最大只能传25。也就是说一页最多25条。而高德的官方接口单次检索最多返回一定数量的POI翻页翻到上限之后即使还有更多数据接口也不会继续返回。这导致关键词检索在数据量大的城市会出现“抓不全”的问题。3.3 用异步协程把并发压到配额上限既然单个请求的QPS有限那怎么才能在合法的前提下跑得更快答案是异步协程加并发控制。Requests库本身是同步阻塞的一个请求发出去必须等响应回来才能发下一个。如果服务端QPS限制是3其实同步就够了但如果限制是50同步就太慢了需要用httpx或aiohttp这类异步库。我常用的一个做法是把所有搜索词和页码组合成一个URL队列用asyncio.Semaphore控制最大并发数再结合一个动态QPS控制器。import asyncio import httpx LIMIT 3 # 不要超过你配额允许的QPS async def fetch_poi(client, sem, url): async with sem: resp await client.get(url, timeout10) return resp.json() async def main(): sem asyncio.Semaphore(LIMIT) async with httpx.AsyncClient() as client: tasks [fetch_poi(client, sem, url) for url in url_list] results await asyncio.gather(*tasks)把并发数压到配额上限的90%左右是最稳妥的留10%的余量给重试机制。另外建议写一个指数退避的重试逻辑当响应码是限流类错误时先等1秒再等2秒再等4秒最多重试3次。实测下来这套策略能把成功率稳定在99%以上。4. Web端爬虫路线requests抓包与hermes签名处理4.1 网页版高德的数据请求链路分析当API配额真的不够用或者需要网页端才有的扩展字段时就得研究网页版高德的接口了。打开高德地图网页版搜索一个关键词打开浏览器开发者工具的网络面板你会看到滑块请求在哗哗地往外发。其中有一部分请求是地图瓦片图片另一部分才是真正返回POI数据的JSON接口。找到那个返回POI列表的XHR请求你会发现它的URL参数非常长里面不光有搜索关键词还有一堆看起来像是随机串的参数。如果你把这串URL直接复制到代码里重新发一次多半会返回参数错误或者校验失败。原因就是请求里带着动态签名。网页版接口的签名机制换过好几轮。有一段时间是简单的MD5拼接后来换成了更复杂的哈希算法就是热词里提到的hermes。hermes参数的生成依赖于前端的JavaScript代码环境、时间戳、请求参数都会参与计算单纯靠抓包复制是没法长期稳定的。4.2 hermes签名参数到底是干什么的简单说hermes是高德Web端用来验证“这个请求是来自真实浏览器”的签名参数。它在每次请求时会根据一组固定参数加上一个动态时间戳计算出来服务端收到请求后会验签验不过就拒绝。我第一次接触这个参数的时候也尝试过去网上找别人写好的破解版脚本。后来发现这条路风险太高签名算法一更新所有脚本就全部失效而且对方服务器做了更严格的风控大规模请求很容易把整个IP段拉黑。我的建议是如果你只是想要少量数据做分析Web端路线可以用Selenium这类浏览器自动化工具来模拟真实操作虽然慢但稳定如果你需要的数据量在正常业务范畴内优先用官方API如果你要做大规模采集那Web端签名逆向是一条性价比很低的路线除非你有充足的时间和精力维护一个逆向团队。4.3 一个可落地的requests解析流程虽然深度逆向不值得推荐但了解一下Web端数据请求的基本链路还是很有必要的。假设你只是想抓取某一个小区域内的POI频率控制在很低的水准可以这样操作先用浏览器手动完成一次搜索把Network面板里的返回数据复制下来做解析确认字段结构。然后把请求参数整理成字典在代码里模拟发送。注意把请求头里的User-Agent、Referer设置成和浏览器一致否则会被风控拦截。import requests headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://www.amap.com/ } resp requests.get(web_api_url, paramsparams, headersheaders, timeout10) data resp.json()这套流程只是改写了请求来源不需要破解签名适用于那种“每天只请求几十次”的低频场景。但凡是超过这个频率我都劝你回去用官方API不然封IP的概率会大幅上升。5. 瓦片下载从在线地图到离线地图的完整方案5.1 瓦片编号规则与行列号计算瓦片地图的原理是把整个世界地图按金字塔层级切分成固定大小的正方形图片。每一层用z表示层级x和y表示该层级下的列号和行号。高德的瓦片和高德的坐标一样基于GCJ-02这点和Google Map的WGS-84瓦片不同混用会出现底图错位。瓦片编号的计算有固定的公式。给定一个经纬度坐标和缩放层级可以算出它对应的瓦片行列号。核心代码如下import math def wgs84_to_tile(lat, lng, zoom): lat_rad math.radians(lat) n 2 ** zoom x int((lng 180.0) / 360.0 * n) y int((1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n) return x, y有了这个函数你只要确定目标区域的左上角和右下角坐标就可以通过循环生成整个矩形区域的所有瓦片编号然后逐个下载。5.2 批量下载与拼接的脚本思路瓦片下载本身就是一个高并发请求图片的过程。高德的瓦片URL有规律可循通常是某个固定的域名加上z/x/y路径。下载的时候控制并发不要太高因为瓦片服务对单一IP的请求频率限制更严格。我自己的下载策略是先把瓦片按z-x-y的目录结构存到本地方便后续直接用。合并的时候要注意瓦片文件名的行列号和最终拼接图片的像素位置是对应的。假设瓦片大小是256x256第x列第y行的瓦片应该放在最终大图的x*256, y*256坐标处用Pillow库的paste方法往下贴就行了。这里要提一个很容易踩的坑高德瓦片的x轴方向和标准TMSTile Map Service的x轴方向一致但标准TMS的y轴是从南向北增大的而多数在线地图瓦片的y轴是从北向南增大的。如果用TMS合并工具去处理高德瓦片拼出来的图上下是颠倒的排查半天才发现是y轴方向的问题。5.3 手机离线包能直接抄近道吗热词里有“高德地图android离线包下载”很多人会问既然高德官方有离线地图功能能不能直接把离线包下载下来解析不就不用爬瓦片了吗思路是对的。高德App的离线地图下载功能确实在本地生成了特定格式的数据包。这些数据包里不光有瓦片图片还有矢量数据、POI索引和路径数据。解析这些格式需要逆向高德App的数据存储结构工程量不小。不过如果你只需要某个城市的瓦片底图走在线瓦片下载路线反而更简单因为瓦片URL规则是公开可研究的不需要逆向App。离线包解析这条路更适合那些需要大量原始矢量数据的专业场景普通项目我劝你放弃。6. 性能与稳定性并发设计和IP代理池的取舍6.1 线程、协程、进程在高德场景下的选择热词里有一句“爬虫并发设计到底哪个好”这个问题的答案完全取决于你爬的是什么。如果是官方APIIO密集型的特征非常明显——大部分时间都花在等待网络上CPU几乎不干活。这种场景协程是性能最优解。用asyncio加httpx就能在一个线程内搞定成百上千个并发请求内存占用极小。如果是瓦片下载IO密集型没跑但图片数据量大建议协程和线程混用。协程负责发请求下载完成后把图片字节流转给线程池做磁盘写入避免大量的文件IO阻塞事件循环。如果是大规模分布式采集那就需要考虑多进程加消息队列了。每个进程跑一组协程进程之间通过Redis之类的队列分发URL这样即使单台机器挂了其他机器还能继续干活。分布式设计看起来复杂但核心逻辑其实就一句话把所有待抓取的任务放到队列里让每个worker从队列里取任务抓到结果后往结果队列里放。6.2 自建代理IP池的完整流程代理IP这个热词出现频率很高但很多人其实用错了方式。所谓代理IP池不是直接去网上随便找一个IP填到代码里而是维护一个动态可用的IP集合。我常用的架构是通过代理服务商API定时抓取一批IP到本地Redis里然后写一个巡检脚本每隔几十秒验证一次哪些IP还活着、响应速度如何把失效的和响应慢的剔除掉。爬虫请求的时候从一个队列里取出代理IP绑定到某个请求上。如果这个请求报错或者被限流就把这个IP打上标记降到最低优先级。高德地图的场景比较特殊。比起IP被封更常见的是QPS超限和签名校验失败。所以代理IP池的作用主要是分摊请求压力让每个IP的并发量都保持在安全阈值以内。如果只是单机小批量采集其实不需要代理IP把并发控制好就够了。6.3 限流、封禁后的降级策略即使做了万全准备也还是有可能被限流甚至封禁。关键是要有降级策略而不是硬刚。我的一般处理顺序是先降并发把当前QPS减半观察一段时间如果还不行就切代理IP如果代理IP也不行就切数据源比如从Web端爬虫切到官方API或者从POI检索切到周边检索。周边检索和关键词检索虽然都是搜POI的接口但底层配额独立A接口被限流的时候B接口往往还能用用交叉检索的方式可以绕开部分限制。还有一个小技巧高德的服务端限流通常是按key维度的。如果项目对数据实时性要求不高可以把任务错峰到凌晨执行那个时间段配额竞争小同样的并发数跑起来要顺畅得多。7. 我踩过的几个坑和现在的建议7.1 坐标偏移引起的数据错位排查第一个坑就是坐标偏移。当时我爬了一批成都的POI数据直接在Leaflet上用WGS-84底图展示结果所有点位全部偏到了成都西边的农田里。一开始以为是爬虫解析出了问题检查了经纬度字段的取值和正则表达式折腾了一个多小时才反应过来是坐标系没转换。那次之后我养成了一个习惯任何地图数据采集工作收到数据的第一时间先做坐标抽样验证。拿几个已知地标的高德坐标放到高德地图上确认位置是否正确然后转成WGS-84放到自己的底图上再验证一次。两套坐标都确认无误后才开始入库。7.2 瓦片下载数量超出预期的教训第二个坑是瓦片下载规模失控。当时规划下载某个城市15级到18级共4个层级的瓦片以为没多少张结果一算吓一跳城市区域每扩大一圈瓦片数量不是线性增长而是指数增长。18级的瓦片数量大约是15级的64倍一个百万人口的城市光18级瓦片就可能有几十万张。那次教训让我明白瓦片下载之前一定要先做规模估算。用目标区域的经纬度跨度结合每一层的分辨率提前算清楚瓦片总量和预计占用的磁盘空间再根据网络带宽估算下载耗时。如果发现耗时超过预期就缩小区域或者降低层级。7.3 现阶段做高德数据采集的个人建议最后说一点我个人对现阶段高德数据采集的看法。热词里有句“AI是爬虫技术的更深层次运用吗”我的回答是爬虫只是取数工具AI是数据价值的放大器。高德地图爬虫爬下来的POI数据配合AI做分类、聚类、选址分析才真正变现。纯爬虫技术本身的天花板远低于“数据采集数据清洗AI分析”这条链条的天花板。所以如果让我给现在的开发者一个建议那就是不要沉迷于逆向高德的签名算法和反爬机制那是门槛高、收益低、风险也不小的事。把精力花在合规的API调用、数据治理、坐标转换、可视化分析上长期来看要值钱得多。另外关于热词里提到的高德Loca组件如果你的最终目标是做地图数据可视化Loca是个好东西。它本身就是高德官方的数据可视化组件支持大规模点数据、热力图、飞线图等效果把爬取到的数据喂给它几行代码就能出图比自己在Canvas上画要省太多事。
返回列表