API调用实战:如何获取高德/百度地图的POI数据并展现在地图上?
很多人第一次做 API调用获取 POI 数据 时,最容易卡住的不是请求发不出去,而是“拿到数据以后怎么继续用”。明明已经从高德或百度返回了商场、医院、地铁站这些地点信息,但一到地图展示阶段,就会遇到分页不全、坐标不一致、点太多卡顿、前端只显示一小部分等问题。
这篇文章只解决一个完整任务:如何获取高德/百度地图的POI数据并展现在地图上。我会按 GIS 项目最常见的流程来写,从密钥准备、接口检索、分页采集、结果整理、地图可视化,到坐标和合规注意点,一步步把这条链路打通,尽量让你拿去就能改成自己的业务脚本。
问题背景:为什么很多 POI 抓取脚本能返回数据,却撑不起正式地图应用
在 WebGIS、选址分析、设施分布图、门店监测和专题看板项目里,POI 往往是最常见的基础数据之一。看起来这件事很简单,无非是“搜一个关键词,再把结果画成点”。但真进到实战,问题马上会变复杂:关键词搜索会漏数,翻页不完整,行政区限制不准确,同一份 POI 结果在不同底图上还可能出现位置偏移。
更关键的是,POI API 返回的并不是直接可交付成果。接口只负责把结果送回来,真正的 GIS 工作还包括字段清洗、去重、按区域统计、点位可视化、聚合渲染和结果复核。也就是说,获取高德/百度地图的POI数据并展现在地图上,本质上不是一个“调一下接口”的问题,而是一条完整的数据处理流程。

核心原理:POI 接口到底在帮你做什么
POI 是 Point of Interest,也就是兴趣点或地点要素,常见包括餐饮、学校、医院、商场、停车场、公交站等。高德和百度提供的地点检索接口,本质上都是一类 Web API:你把关键词、城市、行政区、圆形范围或多边形范围作为参数传给服务器,服务器返回一批结构化地点记录,通常包含名称、地址、分类、经纬度以及部分详情字段。
从 GIS 角度看,这件事可以拆成两层。第一层是“检索逻辑”,也就是你如何定义搜索范围和关键词;第二层是“空间表达”,也就是你如何把这些结果变成地图上的点图层或交互标注。前者决定你拿到的数据是否够全、够准,后者决定地图是否可读、是否能支撑分析。
一句话理解:API 负责把 POI 结果取回来,GIS 工作负责把这些结果变成可验证、可统计、可展示的空间数据。
第一步:先选对检索方式,不要一上来只用关键词搜全城
无论是高德还是百度,地点检索都不只有一种方式。常见的做法包括关键词检索、行政区范围检索、圆形范围检索、多边形范围检索以及详情查询。对于 GIS 项目来说,最重要的不是“接口能不能返回”,而是“这种检索方式是否适合你的业务边界”。
不同检索方式适合什么场景
| 检索方式 | 适合场景 | 优点 | 注意点 |
|---|---|---|---|
| 关键词 + 城市 | 快速摸底某类 POI | 最容易上手 | 结果可能受关键词和排序影响 |
| 行政区检索 | 按市、区县采集门店或设施 | 更贴合业务边界 | 行政层级限制要核对清楚 |
| 圆形范围检索 | 围绕站点、商圈或项目点做周边分析 | 适合服务半径场景 | 圆外但行政上相关的 POI 会被漏掉 |
| 多边形范围检索 | 按园区、街道或自定义片区采集 | 边界更精确 | 需要先准备好范围坐标串 |
| 详情查询 | 对重点 POI 补充详情字段 | 能补足单条信息 | 一般需要先拿到对应 ID 或 UID |
如果你做的是城市医院、便利店或停车场分布图,建议优先考虑按行政区或分区块采集,而不是直接全城关键词搜索。因为官方文档都强调:地点搜索并不是“无上限返回全量数据库”。高德地点搜索文档说明同参数翻页最多支持获取 200 条结果;百度地点检索也有配额和分页限制。如果只靠一次全城搜索,结果通常不完整。
第二步:准备密钥与最小可运行请求
正式写脚本前,先去对应平台申请开发者密钥。高德常见叫 Key,百度常见叫 AK。别把密钥硬编码到公开仓库里,至少放到环境变量或配置文件中。对教学和内部工具来说,这一步常被忽略,但一旦准备上线或多人协作,不做密钥管理会留下明显风险。
高德 POI 检索的最小示例
import os
import requests
key = os.getenv("AMAP_KEY")
url = "https://restapi.amap.com/v3/place/text"
params = {
"keywords": "咖啡厅",
"city": "110000",
"citylimit": "true",
"offset": 20,
"page": 1,
"output": "json",
"key": key
}
resp = requests.get(url, params=params, timeout=20)
data = resp.json()
for poi in data.get("pois", []):
print(poi.get("name"), poi.get("location"))
百度 POI 检索的最小示例
import os
import requests
ak = os.getenv("BAIDU_AK")
url = "https://api.map.baidu.com/place/v2/search"
params = {
"query": "咖啡厅",
"region": "北京",
"output": "json",
"page_size": 20,
"page_num": 0,
"ak": ak
}
resp = requests.get(url, params=params, timeout=20)
data = resp.json()
for poi in data.get("results", []):
print(poi.get("name"), poi.get("location"))
这两段代码的意义不是让你停留在“打印几条结果”,而是确认接口权限、参数拼装和返回结构都正常。只要最小请求能通,后面再扩成翻页采集、结果落盘和前端展示就会稳得多。
第三步:真正实用的关键,是把翻页采集写完整
做 POI 抓取时,最常见的误判就是“接口返回了 20 条,所以这类 POI 一共就这么多”。实际上,大多数平台都会分页返回结果。你必须显式传页码,并持续请求直到没有新结果,或者到达接口允许的翻页上限。
高德翻页采集的实操思路
高德官方文档说明,地点搜索支持关键字、周边、多边形和 ID 查询,但同参数翻页最多支持获取 200 条结果。这意味着如果你在一个大城市里搜“餐饮服务”这类大类词,单次范围检索很容易触顶。这时候更好的办法不是一味翻页,而是拆分范围,例如按区县、街道或网格分块采集。
import os
import requests
key = os.getenv("AMAP_KEY")
url = "https://restapi.amap.com/v3/place/text"
rows = []
for page in range(1, 11):
params = {
"keywords": "便利店",
"city": "110108",
"citylimit": "true",
"offset": 20,
"page": page,
"output": "json",
"key": key
}
data = requests.get(url, params=params, timeout=20).json()
pois = data.get("pois", [])
if not pois:
break
rows.extend(pois)
print("抓取条数:", len(rows))
百度翻页采集的实操思路
百度地点检索 2.0 支持行政区、圆形和多边形检索,也支持分页参数。和高德一样,真正影响完整性的往往不是代码会不会写,而是你有没有把搜索范围拆得足够合理。如果你需要的是“海淀区所有地铁站出入口”“某商圈 3 公里内的停车场”,最好直接按目标场景选检索方式,而不是一把梭。
import os
import requests
ak = os.getenv("BAIDU_AK")
url = "https://api.map.baidu.com/place/v2/search"
rows = []
for page_num in range(0, 10):
params = {
"query": "停车场",
"region": "海淀区",
"output": "json",
"page_size": 20,
"page_num": page_num,
"ak": ak
}
data = requests.get(url, params=params, timeout=20).json()
results = data.get("results", [])
if not results:
break
rows.extend(results)
print("抓取条数:", len(rows))
第四步:把 JSON 结果整理成真正可用的空间数据
接口返回的数据一般是 JSON,但 GIS 工作更常见的下一站是 CSV、GeoJSON、Shapefile 或数据库表。你至少要把 POI 名称、地址、类别、经纬度和来源平台抽取出来,并统一字段命名。否则同一项目里高德和百度结果并排时,后续合并和统计会很难受。
高德常见返回中,经纬度会放在一个字符串字段里,例如 `location`;百度则往往拆成 `lat` 和 `lng`。这就要求你在落盘前先把字段标准化。下面给一个最常见的整理示例。
import csv
with open("poi_output.csv", "w", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerow(["name", "address", "category", "lon", "lat", "source"])
for poi in rows:
location = poi.get("location", "")
if "," in location:
lon, lat = location.split(",")
else:
lon, lat = "", ""
writer.writerow([
poi.get("name", ""),
poi.get("address", ""),
poi.get("type", ""),
lon,
lat,
"amap"
])
如果后续还要在 QGIS、ArcGIS Pro 或 GeoPandas 里继续处理,建议优先导出成 CSV 再转 GeoDataFrame,或者直接输出 GeoJSON。这样既方便核查,也方便之后做空间连接、缓冲区统计和密度制图。
第五步:把 POI 展现在地图上,不要只会加几个静态 Marker
很多初学者做到这里,就停留在“往地图上加一个点”的阶段。但一旦结果有几十条、几百条甚至几千条,简单循环创建标注就容易出现性能问题。更合理的思路是:少量点位可直接标注,大量点位优先考虑聚合、分级显示或按视域懒加载。
用高德 JS API 做基础标注
<div id="map" style="width:100%;height:500px;"></div>
<script src="https://webapi.amap.com/maps?v=2.0&key=YOUR_AMAP_KEY"></script>
<script>
const map = new AMap.Map("map", {
zoom: 12,
center: [116.397428, 39.90923]
});
const pois = [
{ name: "门店A", lng: 116.397428, lat: 39.90923 },
{ name: "门店B", lng: 116.407428, lat: 39.91923 }
];
pois.forEach(item => {
const marker = new AMap.Marker({
position: [item.lng, item.lat],
title: item.name
});
map.add(marker);
});
</script>
用百度 JS API 做基础标注
<div id="map" style="width:100%;height:500px;"></div>
<script src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=YOUR_BAIDU_AK"></script>
<script>
const map = new BMapGL.Map("map");
map.centerAndZoom(new BMapGL.Point(116.404, 39.915), 12);
const points = [
{ name: "门店A", lng: 116.404, lat: 39.915 },
{ name: "门店B", lng: 116.414, lat: 39.925 }
];
points.forEach(item => {
const point = new BMapGL.Point(item.lng, item.lat);
const marker = new BMapGL.Marker(point);
map.addOverlay(marker);
});
</script>
如果你使用的是 Leaflet 或 OpenLayers,而不是高德或百度自家的 JS 地图,也完全可以把接口结果处理成 GeoJSON 再加载。但这时一定要先想清楚坐标系问题,否则“数据明明有了,点却偏到别处去”的问题会非常常见。
第六步:坐标系统和底图一定要先对齐
这是 GIS 场景里最容易被低估、也是最容易翻车的一步。高德常用的是 GCJ-02 坐标体系,百度常用的是 BD-09。你如果把百度返回的坐标直接叠到高德底图,或者把高德结果直接拿去配某些 WGS 84 数据源做叠加,就可能出现整体偏移。
所以在做 地图上展示 POI 数据 之前,先回答三个问题:
- 接口返回的坐标是什么体系。
- 你当前底图使用的坐标是什么体系。
- 后续是否要与外部 GIS 数据叠加分析。
如果只是用同一平台的检索结果叠加同一平台的 JS 地图,通常最省心;如果你要把 POI 导入 QGIS、ArcGIS Pro、PostGIS、Leaflet 或 OpenLayers,再与外部数据一起分析,就要先规划好坐标转换链路。
常见坑点:为什么你的 POI 地图看起来“有结果”,但不能直接交付
只做一次关键词检索,误以为拿到了全量数据
这是最常见的问题。POI 检索接口通常有分页和条数上限,全城大类搜索尤其容易触顶。更稳妥的做法是按行政区、网格或商圈拆分请求,再做去重合并。
忽略关键词歧义,导致结果混进大量无关点
比如搜“学校”,可能会混入培训机构、驾校、幼儿园等不同类型;搜“医院”,又可能混入卫生服务站和药店。真正用于 GIS 分析时,关键词最好和分类字段一起复核,必要时做二次筛选。
拿到坐标就直接展示,没有检查坐标体系
这一点前面已经强调过,但值得再说一次。GIS 项目一旦涉及多源数据叠加,坐标体系错一步,后面所有选址、缓冲区和统计都可能跟着偏。
前端一次性渲染太多点,页面直接卡住
如果你一次加载几千个甚至上万个 POI,直接创建普通 Marker 会明显变慢。更好的做法是做聚合、分级缩放控制、视窗范围查询,或者提前切成 GeoJSON 分块加载。
忽略接口配额、并发和合规边界
高德和百度都对接口使用有配额、权限或服务边界要求。高德官方文档明确说明搜索结果不支持返回全量数据,同参数翻页最多 200 条;百度地点检索文档也列出了配额和并发相关状态码。正式项目一定要先阅读当前官方文档,不要把临时脚本直接当批量生产工具上线。
方法对比:高德、百度,以及“抓完后怎么展示”该怎么选
| 方案 | 适合场景 | 优点 | 注意点 |
|---|---|---|---|
| 高德 Web API + 高德 JS | 国内 WebGIS、门店分布、周边检索 | 平台内链路顺,坐标一致时最省心 | 翻页上限和范围拆分要提前设计 |
| 百度 Place API + 百度 JS | 百度生态应用、地点详情与输入提示场景 | 检索方式完整,适合配合百度地图展示 | 注意 AK 权限和坐标差异 |
| 高德/百度 API + Leaflet/OpenLayers | 自建 WebGIS 前端 | 前端自由度高,便于和自有数据整合 | 必须处理好坐标转换与性能优化 |
| API 结果导入 QGIS/GeoPandas | 专题制图、空间分析、批处理 | 适合和行政区、路网、栅格数据联动 | 需要做字段规范化和投影复核 |
项目检查清单:上线前至少核对这 8 项
- 密钥是否放在环境变量或安全配置中,而不是硬编码公开暴露。
- 检索方式是否和业务边界匹配,是城市、区县、圆形还是多边形。
- 是否实现了完整翻页,而不是只取第一页结果。
- 是否针对大结果集做了分区块采集和去重。
- 是否统一了名称、地址、类别、经纬度字段。
- 是否确认接口坐标体系与底图坐标体系一致。
- 前端是否采用聚合或其他性能优化,而不是无脑加点。
- 是否阅读当前官方文档,确认配额、权限和服务限制。
FAQ:获取 POI 数据并展现在地图上时最常问的几个问题
为什么我搜索“餐饮”或“便利店”,返回结果明显不全?
通常有三种原因:关键词太宽泛、分页没有写完整、单次检索命中了平台返回上限。实务里更常见的解决办法是缩小范围,按区县或网格拆分请求,再做合并去重。
高德和百度拿到的坐标,能直接混着用吗?
不建议直接混用。两者常用坐标体系不同,若叠加到同一底图或与第三方 GIS 数据联合分析,必须先核对并处理坐标转换问题。
POI 数据抓下来后,适合直接放进 QGIS 吗?
可以,而且很常见。你可以先导出 CSV 或 GeoJSON,再在 QGIS 中加载成点图层,然后继续做行政区统计、服务区分析、热点图或专题制图。但前提是先把坐标和字段规范化。
地图上点太多特别卡,最先应该改什么?
优先做聚合和视域内加载。不要一上来就试图把几千个点全部变成独立 Marker。对于业务地图来说,先让用户在缩放层级上看清分布,再逐级展开明细,体验通常更好。
结论:真正有用的 POI 接口实战,不是“会请求”,而是能把结果接进 GIS 流程
如何获取高德/百度地图的POI数据并展现在地图上,表面看是一个 API 调用题,实质上是一个完整的空间数据处理题。你需要同时处理检索方式、分页上限、字段清洗、坐标体系和前端渲染这几个环节,缺一个都容易让结果停留在演示层面。
如果你正准备把这件事落到项目里,最实用的起步方式不是追求一步到位,而是先选一个小场景练透,比如“抓取某区医院 POI,并在地图上做聚合展示”。一旦这条链路跑顺,后面无论是门店监测、设施选址,还是 WebGIS 看板,你都能在这个基础上稳定扩展。