简介:一份面向 GIS 开发者和数据处理人员的百度地图 API 批量地址转坐标工具包,解决 Excel 地址清单批量解析为经纬度的问题,涵盖地理编码、坐标系转换与地图撒点可视化等常见需求。压缩包共 4 个文件,包含两个 JavaScript 库(jquery 与 xlsx 相关)、一个 HTML 操作页面和一个 Excel 数据样例,包体约 375KB,结构精简,适合前端集成或本地快速验证。该资源已有 4433 人学习浏览,说明在物流调度、数据分析和展示场景中有较高参考价值。通过页面调用百度地图地理编码服务,可将地址列逐行转换为坐标,并支持 GCJ-02 与 BD-09 等坐标系换算;配合样例数据,用户可快速理解 Excel 上传、批量解析、结果导出及地图撒点展示的完整流程,减少重复开发工作量。
1. 地址批量转坐标:先从手动查坐标的痛说起
手里握着两千条客户地址,要在地图上打点做配送范围分析。打开网页一个个查,再手工复制经纬度到表格,两千条至少得耗一整天,眼睛酸不说,还容易串行。这份百度地图API地址批量转换坐标(支持Excel上传)的zip包,解决的就是这个场景:本地准备好Excel,脚本读进去,逐条调用地图服务商的地理编码接口,把“某省某市某区某街道几号”转成一串经纬度,再写回新的Excel。整个过程跑完只需要几分钟,而且不依赖网页界面,改参数就能换地址列、换城市限定、换并发速度。
这套东西适合谁用?适合经常和地址数据打交道的人:做门店选址分析、配送范围圈选、客户分布热力图、物流分单,或者是ERP里存量地址缺坐标想补全。新手照着把AK填进去就能跑,熟手则可以改并发、加重试、加坐标系转换。下面几章按“先理解机制,再实操,再排坑”的顺序来,把这份资源从打开到用顺手讲透。
2. 地理编码API机制:AK申请、地址拆分与坐标系三个前提
2.1 AK申请与配额边界
先解决身份问题。地理编码接口不是免费用网页查那种,它要求每个请求带一个AK(access key),相当于调用者的身份证。申请入口在地图服务商的控制台:创建应用时,“应用类型”要选“服务端”,因为脚本是在本地或服务器上发HTTP请求,不是从浏览器页面发。创建后会拿到一对密钥,AK放在请求里,SK用来做SN校验签名,内网工具一般不需要SN,但正式环境建议开启。
IP白名单是第一个容易翻车的地方。创建应用时如果填了白名单,请求来源IP不在名单里,接口会返回权限类错误码。本地调试时填本机出口IP,部署到服务器后要改成服务器出口IP,否则上线第一天接口全部拒绝,日志里全是报错。新应用的每日配额不高,批量几万条时不建议一天跑完,拆成几天更稳,或者分多个应用分摊配额。
请求参数上,核心就几个:address(必填,结构化地址文本)、city(可选,限定在市一级,能显著提升命中率)、output(固定json)、ak(必填)。不带city时,像“中央公园”“国际新城”这类重名地址很可能被解析到别的城市去,加了city等于给解析器画了个范围。
2.2 地址字段拆分:拆列比硬解析更可靠
地理编码接口的解析质量高度依赖地址本身的完整度。最常见的失败案例是地址只有“科技三路66号”这种粒度,没有省市区信息,解析器只能碰运气。国内城市重名不少,道路名也经常在不同城市里重复出现,单靠一列完整地址去解析,解析率能差出二十个百分点。
所以我一般在Excel里把地址拆成四列:省、市、区、详细地址。这样请求时把省市区拼进address参数,就算原始数据里只写了“幸福路29号”,也能拼成“某省某市某区幸福路29号”,命中率立刻上来。如果原始数据已经是一整列“某某省某某市某某区某某路29号”,就不用拆,但要确认分隔符一致,比如带空格还是带逗号。
资源包里给的模板就是分列设计。表头基本长这样:
| 列名 | 示例 | 是否必填 |
|---|---|---|
| id | A0001 | 建议填,方便结果回填 |
| province | 某省 | 强烈建议 |
| city | 某市 | 强烈建议 |
| district | 某区 | 强烈建议 |
| address | 幸福路29号 | 必填 |
| remark | 客户A门店 | 可选 |
如果历史数据只有一整列地址,也没关系,把脚本里的source_col参数改成那一列的列名,代码会自动把整列内容当address传上去,只是解析率会低一点。
2.3 坐标系三兄弟:BD09、GCJ02与WGS84不是一回事
这是批量转坐标过程中最容易事后踩的坑。地图服务商返回的经纬度,用的是自己的坐标系,和GPS设备直接读出来的WGS84、高德用的GCJ02都不完全一致。具体来说,国内公开地图产品出于坐标偏转要求,都会对原始坐标做一次加密偏移,偏移量在几十米到几百米之间。也就是说,同一地址,用GPS测得一组坐标,用高德拿到另一组,用地图服务商地理编码接口又拿到一组,三组不完全重叠。
批量转换出来的坐标如果要在自己底图上打点,得先确认底图的坐标系是哪一种。常见做法是:地图底图是什么坐标系,转换结果就转成什么坐标系。如果后续要把坐标喂给高德或基于GCJ02的平台,需要把接口返回的BD09坐标转成GCJ02。代码片段里一般会带一个转换函数,我这里给出一个很常用的版本:
import math def bd09_to_gcj02(lng, lat): x_pi = 3.14159265358979324 * 3000.0 / 180.0 z = math.sqrt(lng * lng + lat * lat) + 0.00002 * math.sin(lat * x_pi) theta = math.atan2(lat, lng) + 0.000003 * math.cos(lng * x_pi) gcj_lng = z * math.cos(theta) + 0.0065 gcj_lat = z * math.sin(theta) + 0.006 return gcj_lng, gcj_lat上面这个函数把BD09坐标投影到GCJ02,参数lng、lat传入接口返回的经纬度,返回的是转换后的GCJ02经纬度。注意x_pi这个常量是特定算法里的固定值,不要自己改。如果你最后要对接的是GPS设备数据或WGS84底图,还得再做一次GCJ02到WGS84的逆转换,这种逆转换网上有近似公式,精度在米级,对配送圈、热力图来说够用了。
3. 用Excel跑通批量化转换:脚本、字段映射与断点续跑
3.1 Excel模板这样设计
打开资源包里的模板文件,你会看到Sheet“地址表”已经预设好列格式。第一行是表头,从第二行开始放数据。关键点在于:源文件不要求Excel格式必须是xlsx,csv也可以,但编码要注意,UTF-8编码的csv在Excel里打开可能会乱码,读取时建议指定encoding='utf-8-sig'。
字段映射上,脚本默认读“province”“city”“district”“address”四列,拼成完整地址去请求。如果你的表头叫“省份”“城市”“区县”,只需要在脚本开头把映射字典改一下,不需要动数据结构。资源包里有一个config.py,里面放着AK、文件路径、字段映射和限速参数,改配置比改代码安全得多。
模板下方还预留了一个Sheet“结果输出”,脚本跑完会把结果写入这个Sheet,保留原始地址列的同时,新增lng、lat、status、message四列。status为0表示成功,非0表示失败,这样一眼能看到哪些行要人工复查。
3.2 核心转换脚本与参数说明
整个批量过程的核心是一个循环:读一行地址,拼参数,发请求,解析JSON,写结果。下面这版脚本是资源包里最常用的形态,我加了一些注释:
import csv import json import time import requests AK = "你的AK" INPUT_FILE = "address_input.csv" OUTPUT_FILE = "result_output.csv" def geocode(address, city): url = "https://api.map.baidu.com/geocoding/v3/" params = { "address": address, "city": city, "output": "json", "ak": AK, } for attempt in range(3): # 最多重试3次 try: resp = requests.get(url, params=params, timeout=5) data = resp.json() if data.get("status") == 0: loc = data["result"]["location"] return loc["lng"], loc["lat"], 0, "" else: return None, None, data.get("status"), data.get("message") except requests.exceptions.RequestException: time.sleep(1) # 网络抖动时等一下再重试 return None, None, -1, "network error" with open(INPUT_FILE, "r", encoding="utf-8-sig") as fin, \ open(OUTPUT_FILE, "w", newline='', encoding="utf-8-sig") as fout: reader = csv.DictReader(fin) writer = csv.writer(fout) writer.writerow(["id", "address", "lng", "lat", "status", "message"]) for row in reader: full_addr = "{}{}{}{}".format( row.get("province", ""), row.get("city", ""), row.get("district", ""), row.get("address", "") ) lng, lat, status, msg = geocode(full_addr, row.get("city", "")) writer.writerow([row.get("id", ""), full_addr, lng, lat, status, msg]) time.sleep(0.3) # 用sleep控制QPS这段逻辑并不复杂,但有几个参数值得停下来解释。timeout=5是给每个请求的等待上限,地址解析一般一两秒内返回,超过5秒说明网络或服务端异常,这时重试比干等有价值。time.sleep(0.3)是在控制请求频率,大部分应用默认QPS不高,不sleep直接循环,几百条后一定会触发限频。for attempt in range(3)做的是重试保护,网络请求丢包或服务端偶发超时,重试一次往往就成功了。
如果你要处理的文件很大,建议把requests.get换成requests.Session(),复用HTTP连接能减少握手开销,速度会快一些。Session用法就是把requests.get(url)改成session.get(url),开头创建session = requests.Session()。
3.3 断点续跑:中断了不用从头再来
批量转换有个很实际的痛点:跑到两千条时网络闪断,或者配额耗尽报错,脚本停下来,你发现前五百条结果没保存,又要从第一行开始跑,浪费配额和时间。所以资源包里的输出不是最后一次性写入,而是每处理一行就追加写入一行,这就是断点续跑的基础。
实现上依赖CSV的追加写模式。重跑时先读一遍输出文件,把已经成功的id收进一个集合,遇到集合里已有的id就直接跳过。这里的关键是“已经成功的id”而不是“所有已存在的id”,因为有些行可能返回了失败状态,重跑时还得再试一次。代码里可以用一个简单判断:
done_ids = set() with open(OUTPUT_FILE, "r", encoding="utf-8-sig") as f: for line in f: parts = line.strip().split(",") if len(parts) >= 5 and parts[4] == "0": done_ids.add(parts[0])这段代码在打开输入文件前先执行,把输出文件里status字段为0的记录对应的id收集到done_ids。后面主循环里每处理一行前先判断:if row["id"] in done_ids: continue。这样即使跑到五千行被限频,修复配额后重跑,脚本会自动跳过已经成功转好的行,只补失败部分。
4. 批量转坐标常见问题排查:五条踩坑记录
4.1 解析率只有六成:不是接口不行,是地址粒度不够
现象:两千条地址跑完,只有一千二百条返回成功,失败的全是“unable to resolve address”之类提示。原因:这些失败地址大多只有“某路某号”或“某小区某栋”,没有省市区前置信息,服务商解析器没法确认它在哪个城市。解决:把省市区三列数据拼到address参数里,不要只传详细地址;对于仍然解析失败的,再试着把“路”改成“大道”“街”,因为POI库里道路命名有一定的别名差异。做完这两步,解析率通常能到九成以上。
4.2 跑到一半开始报“APP_INVALID_AK”或“QUOTA_EXHAUSTED”
现象:前一百条正常,第一百零一条开始连续报权限或配额错误。原因:这个报错不是AK真的错了,而是小时级或分钟级配额触顶,服务端开始拒绝请求。另一个可能是IP白名单在运行中途被改过,或者代理出口IP发生了变化。解决:先看错误码,配额类错误就停脚本、等配额窗口刷新,同时把time.sleep间隔调大;权限类错误就检查AK是否被复制少了一位,以及白名单里的IP和当前出口IP是否一致。
4.3 坐标和真实位置偏了几百米
现象:转换出来坐标在高德地图上打开,标记点落在隔壁街区,偏差明显。原因:坐标系混用。接口返回的是BD09,高德底图默认是GCJ02,两个坐标系之间存在偏移,不是解析错了。解决:先确认查看底图的坐标系,如果是高德,用前面给的bd09_to_gcj02函数转一次再打点;如果是GPS设备对比,还要继续转成WGS84。这个问题靠肉眼很难发现,但等你把数据导进GIS软件叠底图时会非常明显。
4.4 同一条地址重复转换,两次结果不一样
现象:同一行数据手动跑了两遍,返回的坐标相差几百米。原因:地址指向的区域范围较大,比如“某市某区某科技园”,解析器每次都从POI库里选一个匹配点,匹配逻辑可能命中不同的园区门或楼栋,结果自然不同。解决:在数据准备阶段对地址做去重,同一id只转换一次,后面的重复行直接复用第一次的结果。去重逻辑放在读CSV时就要做,别等跑完再手工查重。
4.5 Excel里的地址列变成科学计数法
现象:原始Excel中地址或门牌号列被显示成“3.01E+15”,转换时读到奇怪的值,解析必失败。原因:Excel自动把超过11位的数字列转成科学计数法,常见于身份证号、长门店编号或纯数字门牌地址。解决:读取文件时用pandas.read_excel(..., dtype=str)强制按文本读取,或者把Excel源文件里该列的单元格格式改成“文本”再保存。这个坑在资源使用中最容易忽略,因为它不影响前几条数据,等到中间某条报错时才暴露。
5. 转完坐标之后:结果回填、逆编校验与落图验证
批量转换只是第一步,坐标拿到手之后还要做数据校验和落图。我会先做三件事:把坐标分列回填到原Excel,做一次逆地理编码校验,再把CSV丢进GIS软件看分布。
坐标回填最简单,把输出CSV里的lng、lat两列复制回原表,新增“经度”“纬度”两列,按id匹配就行。如果原始表里有几万行,建议用pandas的merge做关联,比Excel的VLOOKUP稳定。
逆地理编码校验是我比较依赖的一个验证手段:把转换出的坐标再调一次逆地理编码接口,让它把坐标反解成地址文本,然后和原始地址做关键词比对。比如原地址是“某市某区幸福路29号”,反解析回来如果返回“某市某区幸福路29号”,说明坐标质量高;如果反解析回来是隔壁路名,就要人工看。下面的代码片段可以做一个简单的抽样校验:
def reverse_geocode(lng, lat): url = "https://api.map.baidu.com/reverse_geocoding/v3/" params = {"location": f"{lat},{lng}", "output": "json", "ak": AK} resp = requests.get(url, params=params, timeout=5) data = resp.json() if data.get("status") == 0: return data["result"]["formatted_address"] return ""这里location参数的格式是“纬度,经度”,顺序反了会返回错误。拿到的formatted_address和原地址做模糊匹配,匹配度低于阈值的行拉出来人工复核。抽样比全量快,随机抽50到100条就能暴露大部分系统性偏移。
最后把带坐标的CSV拖进QGIS,叠加行政区边界图层,看落点有没有飞出城市边界。坐标落到海里、落到隔壁城市的,基本都是地址拆分或坐标系转换出了问题。这套流程整套走下来,数据质量基本可放心。
从那以后我每次批量转换前都会先抽样20行做格式验证,跑完再抽查反转校验,这套流程基本没再翻车。希望帮到你。
本文还有配套的精品资源,点击获取