高德POI数据抓取与空间分析实战:从接口到可视化
2026/9/10 18:30:55 网站建设 项目流程

先交代一下背景:这个项目是做商业数据复盘时临时加进来的,目标是把高德地图广东省2026年2月份的POI数据完整拉下来,做一次全量概览。POI数据在地图里看着就是一个点,但在数据工程和数据分析里,它牵扯到接口配额、坐标系转换、类别体系归一、空间聚合、可视化呈现一整条链路。这篇记录不是讲某个工具的简单用法,而是把我从抓数到出报告的完整过程、踩过的坑、以及最后沉淀下来的分析口径都写出来。准备做城市商业分析、网点选址或者空间数据可视化的同学,可以重点参考。

1. 项目整体思路与技术选型

1.1 项目背景:为什么要盯广东省POI

广东省的特点很明显:经济总量大、城市层级多、商业形态差异极大。深圳和广州是典型的高密度核心城市,东莞、佛山是制造业和镇域经济特色突出的城市,粤东粤西粤北又是完全不同的消费场景。要用数据理解这种差异,POI是最容易拿到的空间数据之一。

但POI不是单纯拿来画个点图就完事。我更关注的是:全省餐饮、购物、生活服务、医疗、教育、文体类设施到底分布在哪,各类设施在不同城市的密度差有多大,哪些区域是设施高度集聚的商圈。这些信息对零售选址、城市配套评估、行业竞争分析都有直接价值。

这次做的是2026年2月的月度快照分析,重点不是做一次性的全省地图,而是把分析流程打磨成可以每月复跑的数据工程任务。因为高德开放平台返回的是实时POI库,并没有“某个月的历史POI”这种官方概念,所以我采取了定期抓取快照的方式。代码、清洗流程、指标口径都按可复用来设计。

1.2 技术选型:高德API + Python + PostGIS

整个链路不长,但每一环都有讲究。

  • 数据获取:高德开放平台Web服务API,官方接口,字段稳定,不需要爬虫。
  • 数据清洗和统计:Python + pandas,适合快速做字段整理和交叉统计。
  • 空间存储和聚合:PostgreSQL + PostGIS,省市级数据量虽然不大,但后续要做空间网格、缓冲区分析,直接上空间数据库会省很多事。
  • 可视化:QGIS出静态专题图,Kepler.gl做交互式浏览,Excel只是最终汇总表的辅助工具。

选高德API而不是去爬网页端POI,主要原因是规范和效率。网页端即使拿到数据,破解参数和反爬机制的成本很高,而且高德网页端的数据结构随时可能变。用官方API虽然要申请Key,但返回字段是固定的JSON结构,用代码分页抓取就能稳定拿到全省数据。

如果只做小范围分析,比如一个商圈、几条街道,直接用浏览器里的采集器拖拽导出也可以。但这次是全省21个地级市,20个一级POI分类,如果靠人工操作采集器,几千次请求很难稳定跑完。用脚本按adcode和类型分片抓取,再把结果写入数据库,才是通用做法。

1.3 数据范围与时间口径

先说数据范围,避免后面分析口径说不清楚。

  • 空间范围:广东省全部21个地级市,按高德adcode划分,不手动修改边界。
  • 类别范围:采用高德POI一级分类,包括汽车服务、餐饮服务、购物服务、生活服务、体育休闲、医疗保健、住宿服务、风景名胜、商务住宅、政府机构及社会团体、科教文化服务、交通设施服务、金融保险服务、公司企业、道路附属设施、地名地址信息、公共设施等。
  • 时间口径:以2026年2月28日抓取到的在线快照为准。
  • 对比口径:为了看春节前后变化,额外抓取了2026年1月31日快照做增量对比。

这里有个很重要的前提:高德API返回的是“抓取时刻”的数据状态,而不是历史月度库。所以所谓“2026年2月份POI数据”,本质上是2月底这个时间点上高德库里能查到的最新POI集合。如果要严谨地说“月度变化”,必须自己在每个月固定时间点拉一次快照,隔几个月再做对比。这次我只做了1月和2月两个快照,分析结论也只限定在这两期数据之间。

2. 数据获取与清洗

2.1 高德POI接口的抓取方式

高德开放平台里最常用的是搜索POI接口,我这次用的是/v3/place/text。参数不算复杂,但有几个地方必须注意。

import requests import pandas as pd import time AMAP_KEY = "你的高德Key" def fetch_amap_poi(adcode, types, page): url = "https://restapi.amap.com/v3/place/text" params = { "key": AMAP_KEY, "types": types, "city": adcode, "citylimit": "true", "offset": 25, "page": page, "extensions": "all" } resp = requests.get(url, params=params, timeout=10) data = resp.json() if data.get("status") == "1": return data.get("pois", []) else: raise RuntimeError(data.get("info")) # 示例:拉取广州市餐饮服务第一页 pois = fetch_amap_poi("440100", "050000", 1) df = pd.DataFrame(pois) print(df.shape)

这里几个参数值得单独说:

  • city要传市级adcode,不能只传“广东省”。高德接口对省级范围支持不理想,直接传省级编码很容易漏数据。我是遍历21个地级市分别抓取。
  • citylimit=true是限制结果只在指定城市内,不传的话会混入周边城市的POI。
  • offset单页最大25条,page最大100页,所以单个城市单类目最多只能翻2500条。如果某类POI超过2500条,必须再往下拆分二级分类或按行政区继续细分。
  • extensions=all会返回更完整的字段,比如business_areaimportance等;如果只需要基础点,用base也能省流量。

抓取不是一次跑完就结束。我按“城市+一级分类”建立任务清单,每抓一个组合写一个CSV,后面统一导入PostGIS。这样中间断了不用从头开始,重试失败任务就能续跑。

2.2 POI字段里到底藏着什么

高德返回的POI字段很多,但真正做分析时常用的是这几个:

字段说明分析用途
idPOI唯一标识去重、关联
name名称品牌清洗
location经纬度,GCJ-02坐标系空间分析
pname / cityname / adname省/市/区名称行政归集
address地址文本校验
type分类字符串,如“餐饮服务;中餐厅”类别统计
typecode分类编码类别归一
business_area商圈信息商圈分析
importance重要程度分级展示
tel电话手工抽查验证

刚开始我也只拉idnamelocationtype,后来发现typecodetype更稳定。因为type是中文文本,不同时期可能有表述差异,而typecode是编码,做代码判断更可靠。

2.3 清洗阶段的三个硬骨头

抓回来不是直接能分析的,我总结下来有三个硬骨头。

第一个是去重。用id去重是最基础的,但只靠id不够。接口分页、多城市交叉抓取时,同一个POI可能被重复抓多次。我采用的去重字段是id + name + location + typecode,保留第一次出现的记录。这样既能去掉完全重复的点,也能去掉同id但坐标或分类发生变化的旧记录。

第二个是字段规整。location是一个“经度,纬度”字符串,要先拆成两个数值列。type要先取“;”分隔的第一段作为一级分类,typecode前两位作为编码分类。这些操作不复杂,但如果不提前做,后面每个统计都会带着脏数据。

第三个是空间过滤。citylimit=true并不保证结果100%落在目标城市内,个别点可能因为边界问题落在邻市。我写入PostGIS后,再用高德的行政区边界做一次空间Join,把明显超出广东省范围的点剔除掉。这一步在市级分析中很重要,尤其东莞、惠州这些交界地带。

2.4 坐标转换:高德坐标不能直接当WGS84用

这是这次项目最容易踩的坑。高德在线地图和POI接口返回的经纬度都是GCJ-02坐标系,也就是俗称的“火星坐标系”。如果直接把坐标当成WGS84去和OSM、GeoJSON或者其他GPS采集数据叠加,点位会整体偏移几百米,严重的会偏到道路另一侧甚至海里。

我的处理方式是在清洗阶段统一做转换。用Python的gcoord库,把GCJ-02转成WGS84:

import gcoord def gcj02_to_wgs84(lon, lat): result = gcoord.transform( gcoord.GCJ02, gcoord.WGS84, [float(lon), float(lat)] ) return result[0], result[1]

但这里有个细节:如果你最后的成果图仍然使用高德底图,那可以不转。因为高德瓦片和高德POI都是GCJ-02,天然对齐。只有当你需要把POI和WGS84数据叠在一起,或者导出GeoJSON放到其他平台时,才必须转换。我这次因为后续要和标准行政边界、城市路网叠加,所以统一转了。

3. 核心分析维度与方法

3.1 类别金字塔:先看结构再看数量

POI全量数据的第一个分析维度一定是类别结构。我把清洗后的广东省POI按一级分类聚合,得到的结果符合直觉:餐饮服务类占比最高,购物服务类第二,生活服务类第三。这三类加起来占了全省POI的一半以上。

只看占比还不够,我习惯做“类别金字塔”:

  • 第一层:20个一级分类的全省总量排序。
  • 第二层:每个一级分类下的二级分类结构,比如“购物服务”里到底是商场多还是便利店多。
  • 第三层:每个二级分类在不同城市的密度。

用这个办法能快速发现异常。比如某市“公司企业”类POI量异常大,可能不是真的企业多,而是产业园区把每个门牌都单独录入成了POI。分析时需要结合实地调研或者栅格影像抽样验证。

3.2 城市对比:总量不等于密度

广东省21个地级市,POI总量差距非常大。深圳、广州在总量上领先,东莞、佛山紧随其后。但做城市对比时,只比总量没有意义,必须引入密度指标。

我常用两个密度口径:

  • 每万人POI数:POI总数 / 常住人口(万人)。
  • 每平方公里POI数:POI总数 / 建成区面积(平方公里)。

两种口径的排序结果完全不同。有的城市人口多、总面积大,人均POI数偏低;但如果只看建成区密度,核心城区并不差。所以报告里我会同时呈现总量、人均、地均三个指标,不让业务方只盯着一个数字做判断。

人口和建成区面积不是高德API提供的,需要从统计部门公开数据获取。这一步要注意数据年份尽量和POI快照时间接近,如果拿不到同月数据,至少要在报告里标注清楚用的是哪一年的人口基数和面积口径。

3.3 网格化与热力分析:从点到面

POI是点数据,但做空间分析时很难直接看几十万个点。我一般先用PostGIS把点聚合到网格。

这里可以按500米×500米网格统计POI数量,也可以按1公里网格看大区域差异。深圳、广州中心城区用500米网格能看出明显的商圈边界,但粤东粤西地市用500米网格可能很多格子都是空的,这时候需要提高聚合尺度。

网格统计之后再做热点分析,我用的是核密度估计。QGIS里直接有Heatmap工具,关键参数是半径和像元大小。我这次城市热点图用的是500米半径,像元大小50米。如果半径过大,商圈边界会模糊;半径过小,热点又碎成一堆小斑块。实际操作中,我建议先跑300米、500米、800米三版对比,再根据城市尺度选择最合适的一版。

热力图适合展示“哪里密集”,但它不回答“这个热点是不是独立商圈”。后续如果要识别具体商圈边界,我会再跑HDBSCAN聚类,按密度连通性划分簇。这次概览阶段没有做那么重,但数据已经准备好,随时可以扩展。

3.4 2026年2月增量:春节效应怎么看

2月正好包含春节,我一开始想通过1月底和2月底两次快照的差异看春节效应。实际跑下来发现,POI月度增量不能直接等同于商业活跃度。

原因有几个:第一,POI数据更新有审核周期,新店可能延迟几周才上线;第二,春节前后大量临时摊位、移动摊点不会进入POI库;第三,有些门店虽然在春节期间歇业或关闭,但高德库里不一定立即下线。

所以我对增量的解读比较保守。我只看几个相对敏感的二级分类,比如餐饮服务下的“休闲餐饮”、住宿服务下的“宾馆酒店”、购物服务下的“特色商业街”,统计它们的数量变化和新增POI的分布区域。如果一个区域在2月新增POI明显集中,再去结合商圈活动、交通客流做进一步验证。

这个环节最主要的产出不是“春节导致POI增减了多少”,而是建立一套月度快照对比的规则:每个月固定日期拉数、固定清洗逻辑、固定指标口径。连续跑半年后,月度数据才能真正回答趋势问题。

4. 可视化与报告产出

4.1 QGIS加载高德底图

分析结果最终要落到地图上。QGIS里可以通过XYZ Tiles加载高德瓦片,模板地址可以直接填:

https://webrd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}

在QGIS左侧浏览器面板里找到XYZ Tiles,右键新建连接,把上面的地址粘贴进去,确定后就能看到高德底图。

这里要注意坐标系。QGIS默认使用EPSG:3857显示在线瓦片,如果你的POI点是WGS84经纬度,需要先重投影到3857再叠加。虽然QGIS会自动做动态坐标系转换,但在大数据量渲染时,统一投影到3857会更快,出图也不容易出现偏移。

4.2 静态图与交互图的取舍

报告用的静态图,我用QGIS出专题图。主要做两类:

  • 分级设色图:按每个区县或网格的POI数量分级着色。
  • 热力图:用于展示全省和各城市的设施集聚热点。

交互式地图我用Kepler.gl。不需要写太多前端代码,直接把聚合后的GeoJSON拖进去就能配图层。但几十万级别的点数据直接拖进去会卡,所以交互图我也会先做网格聚合,再用网格中心点或者面要素渲染。

出图排版要克制。一张图只表达一个核心结论,图例、比例尺、指北针必须完整。颜色方案优先使用同一色系的渐变,不搞花哨的红绿对比,避免因为印刷或屏幕差异造成误导。

4.3 指标口径必须和业务对齐

做数据分析最怕的不是图丑,而是口径不统一。这次报告里我专门建了一个指标说明表:

指标口径说明
POI总量清洗去重后的有效POI数
餐饮类POI高德一级分类为“餐饮服务”的POI数
每万人POI数POI总量 / 常住人口(万人)
每平方公里POI数POI总量 / 建成区面积(平方公里)
热点区域核密度估计值前10%的网格集合

为什么强调这个?因为业务方可能拿来和之前用百度地图或者其他数据源做的分析对比。不同地图厂商的POI分类、收录范围、坐标体系都不一样,指标数值不能直接跨源比较。我在报告开头就写清楚了:本报告所有结论仅针对高德地图数据源和2026年2月快照,不能直接与其他地图数据源混用。

5. 常见问题与排查记录

5.1 抓取配额和中断问题

连续跑几万次请求后,最常见的报错是配额超限和QPS被限制。高德控制台可以看到每个Key的配额,个人开发者的免费配额有限,如果抓全量全省数据,一天之内大概率爆掉。

我的做法是:

  • 每个请求之间加time.sleep(0.2~0.5),降低并发压力。
  • 失败请求自动重试,最多重试3次,重试间隔按1秒、3秒、10秒递增。
  • 每个城市和类目单独存CSV,抓完一个标记一个,断了继续跑而不是从头开始。
  • 如果配额确实不够,就把任务拆到多天,或者使用另一个已经完成企业认证的Key。

5.2 经纬度漂到海上

这个问题通常不是抓取错,而是坐标系没处理对。GCJ-02坐标在WGS84底图上显示时会整体偏移,沿海城市有些POI会直接偏进海里。遇到这种情况,先检查项目里是否统一做了坐标转换,再检查QGIS或Python加载数据时是否设置了正确的坐标系。

还有一个小概率情况:高德个别POI的location字段本身就是缺失的,需要过滤掉;如果直接在pandas里把空值转成NaN,后续空间计算会报错。我的清洗脚本里对location为空的行直接删除,并在日志里记录删除数量。

5.3 分类归属不一致

同一家连锁品牌,在不同城市可能被归类到不同一级分类。比如部分便利店被归到“购物服务”,部分被归到“生活服务”。如果按一级分类统计全国或全省网点数量,结果会失真。

我的应对方式是:对关键品牌单独做字典清洗。把品牌名和统一行业归属做成一张映射表,在原始分类基础上覆盖。比如某连锁药店如果被错归到“公司企业”,我会根据品牌名把它修正到“医疗保健服务”。这份映射表是项目里最值钱的资产之一,后面每个月复跑都要维护。

5.4 热力图参数怎么选

核密度估计里最常被问的是“半径设多大”。QGIS Heatmap插件的半径参数含义是搜索半径,半径越大,热点越平滑,但空间分辨率越低。

我这几版对比的经验是:

  • 300米半径:适合街道级商圈分析,热点细碎。
  • 500米半径:适合城市级概览,能识别主要商圈。
  • 800米到1000米半径:适合大区域比较,会弱化局部细节。

如果出图后整个画面都是红的,说明半径过大;如果热点全是噪点,说明半径过小。建议在城市整体分析中先用500米,再针对重点关注区域单独出300米细版。

6. 复盘:这次踩过最值得说的坑

整个项目最花时间的不是抓数,而是把“能用的数据”和“能分析的数据”对齐。

我第一版清洗脚本只做了id去重和location拆分,结果在深圳和广州出图时,发现部分POI叠加到高德底图上有点偏移,排查了很久才发现是坐标系问题。后来我把坐标转换放到了清洗流程最前面,每次抓完数据直接落成WGS84,后面所有分析都基于统一坐标系,再没出过类似的低级错误。

另外一点是不要把POI变化理解成实时商业变化。高德POI的更新存在审核周期,新增门店不一定当天上线,下架门店也不一定当天消失。报告中提到任何“变化”时,我都会强调这是两个快照间的“可见变化”,而不是“真实经营变化”。这个数据伦理看起来很小,但在业务汇报时非常重要。

这个项目后续如果能继续做,我会加上更多维度的交叉验证:比如把POI数据和夜间灯光、交通拥堵指数、地块租金放在一起做回归分析,而不是只看POI本身。数据工程和数据科学之间没有明确边界,关键是你有没有把数据口径、坐标系、清洗逻辑这些基本功做扎实。空间数据分析尤其如此,底子不牢,后面所有漂亮的图都是空中楼阁。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询