简介:面向小程序开发者的地址解析项目源码,基于微信小程序与腾讯云接口,将用户输入的非结构化收货地址自动识别为省市区等标准地理信息,解决电商、物流场景中地址录入不规范的问题。资源包为zip压缩格式,共18个文件,涵盖6个JS逻辑文件(含sha1签名、base64编码、工具函数)、5个JSON配置页面数据、3个WXSS样式、2个WXML页面结构,整体约16KB,便于快速查看核心代码。项目重点演示了腾讯云接口接入中的HMAC-SHA1签名、Base64编码、请求构造与响应解析流程,虽确认按钮事件未完全实现,但可作为地址解析模块二次开发的基础模板。此外,工程内包含页面结构、样式与工具脚本,可清晰查看从输入地址到提交解析的完整调用链路,也便于替换或扩展自己的业务逻辑。目前已有8239人学习下载,适合需要在小程序中集成地址标准化能力的中级开发者参考。
1. 地址智能识别是什么:一条随手黏贴的收货地址,凭什么直接变成省市区字段
在电商小程序里填收货地址,用户不会按省、市、区一级一级去选,而是从别处复制一大段地址直接粘进来:“广东省深圳市南山区科技园南区T3栋腾讯大厦 张三 13800138000”。拿到这段自由文本的后端,需要省市区字段去做区域库存分配、运费计算和快递面单打印。这个标题做的事情就是把用户输入的这段自由文本交给腾讯云API做智能识别,自动解析出省、市、区、详细地址四层结构,再回填成标准化格式的收货地址表单。它适合电商小程序、外卖订餐、快递代收点、企业内部订单系统这些场景,解决“拿到一段地址却拿不到结构化字段”的窘迫。初版怎么搭、参数怎么设、哪些地址解析必然翻车,这篇直接讲透。
2. 选型与原理:为什么正则拆地址会翻车,腾讯云 API 拆到什么粒度
2.1 先承认现实:纯正则拆地址是一个永远填不完的坑
先说一个结论:如果只做内部demo、只处理某一个城市的固定格式,正则够用;一旦地址来源不可控,正则方案必然翻车。
正则拆地址的常见做法是按关键词切字符串,把“省”“市”“区/县”当作分隔符,比如/^(.*?省)?(.*?市)?(.*?[区县])?(.*)$/这样去匹配。听着合理,实际跑起来问题一大堆:
- 用户只写“中关村大街27号”,没有省也没有市,正则不知道这是哪个直辖市的地址;
- “朝阳区”在北京和长春都有,“三亚市”这种地名也可能被切错,因为“三”和“亚”的边界判断依赖上下文;
- “内蒙古自治区”这种省级全称不以“省”结尾,如果字典规则只匹配“省”字,直接漏匹配;
- 地址里带着“收货人:张先生 13800138000”,正则不知道哪些部分属于地址、哪些属于联系人信息。
这些不是正则表达能力的问题,而是地址本身依赖行政区划知识库和大量真实语料。腾讯云NLP的地址解析能力,本质是把非结构化地址文本映射到结构化地址字段的序列标注模型,它见过足够多的地址写法,能处理“XX省XX自治州”“XX市XX区XX街道”这类变体。选它而不是自己写正则,核心原因是地址文本的写法千奇百怪,规则维护成本远高于调用一个成熟接口。
2.2 腾讯云 API 返回的字段粒度,正好对得上小程序表单
这个标题里写的是“使用腾讯云api”,落地时我一般指向腾讯云自然语言处理产品线里的地址解析接口。它会返回类似这样的 JSON 结构:
{ "Province": "北京市", "City": "北京市", "District": "海淀区", "Address": "中关村大街27号", "ErrorCode": 0 }注意细节:直辖市处理上,北京市的省级和市级会同时返回“北京市”;区级字段对应“海淀区”;详细街道和门牌号落到“Address”。这套字段粒度刚好和小程序端“省市区三级联动选择器 + 详细地址输入框”一一对应。
这里要特别和“地址解析坐标”区分开。腾讯位置服务也有一个接口叫“地址解析”,但输入地址文本输出的是经纬度坐标,适合做地图定位;而这个项目要的是“省市区 + 详细地址”的文本拆解,方向完全不同。如果选错接口,数据库里存了一堆经纬度坐标,后面做区域统计才发现数据结构整个不对,返工成本很高。
2.3 小程序直接调腾讯云 API,为什么一定要走一层代理
小程序前端不能直接带着腾讯云的 secretId/secretKey 去调用 API,两个硬性原因:
- 密钥放前端等于公开,任何人截到请求就能白嫖你的腾讯云配额;
- 微信小程序
wx.request只认配置过白名单的 HTTPS 域名,而腾讯云 API 的签名流程在小程序端实现非常繁琐。
常见做法有两个:如果你有自己的后端服务,就在后端封装一个地址解析接口,小程序调自己后端;如果没有后端,直接用微信云开发的云函数转发,云函数天然不需要配请求域名白名单。下面两章我按“云函数转发”的路径来写,这也是最快跑通、最适合个人开发者和中小团队的方式。
3. 跑通最小链路:小程序输入 → 云函数转发 → 地址解析返回省市区
3.1 准备腾讯云侧的三样东西:开通服务、拿密钥、建云函数
先做三件准备工作,整个流程不超过十分钟。
第一,在腾讯云控制台开通自然语言处理 NLP 服务,确认里面有地址解析/地址识别这一类接口。控制台里的 API Explorer 可以直接输入一条地址试调用,先把返回字段确认清楚再写代码。
第二,在访问管理 CAM 里创建一个子账号密钥,分配QcloudNLPPermission或最小权限的产品策略。不建议把主账号密钥直接丢进代码里跑,出了问题不好切权限,密钥泄露风险也更大。
第三,在小程序开发者工具里开通“云开发”,创建云函数,函数名比如叫parseAddress。云函数的运行环境我用 Node.js 的tencentcloud-sdk-nodejs官方 SDK,版本装 4.x 以上。在云函数目录里安装依赖:
npm init -y npm install tencentcloud-sdk-nodejs安装完成后在云函数入口index.js里写调用逻辑:
const tencentcloud = require("tencentcloud-sdk-nodejs"); // 云函数入口,event 是小程序端 wx.cloud.callFunction 传过来的参数 exports.main = async (event) => { // 从参数里取出原始地址文本并去掉首尾空格 const rawAddress = (event.address || "").trim(); if (!rawAddress) { return { ok: false, msg: "地址不能为空" }; } // 初始化 NLP 客户端,密钥从云函数环境变量读取,别硬编码 const NlpClient = tencentcloud.nlp.v20190408.Client; const client = new NlpClient({ credential: { secretId: process.env.TENCENT_SECRET_ID, secretKey: process.env.TENCENT_SECRET_KEY, }, region: "ap-guangzhou", profile: { httpProfile: { endpoint: "nlp.tencentcloudapi.com" }, }, }); // 地址解析请求,具体接口名以你开通产品在 API Explorer 里的为准 const params = { Text: rawAddress }; const result = await client.AddressParse(params); return { ok: true, data: result }; };逻辑说明:云函数接收小程序传来的event.address,先 trim 去掉首尾空格;再用环境变量里的密钥初始化官方 SDK 客户端;最后把原文传给地址解析接口并原样返回。密钥放process.env是为了避免代码提交时把密钥带进仓库,云函数控制台里可以直接配置环境变量,安全且方便轮换。
参数说明:region用ap-guangzhou,NLP 在广州区有可用区,换成你资源所在的区域也可以,但影响不大;endpoint显式指定为nlp.tencentcloudapi.com,不让 SDK 自动解析域名,省掉一次 DNS 解析延迟;AddressParse这个接口名如果在你开通的服务里不叫这个,以 API Explorer 显示为准,把调用行替换成对应名称即可。
3.2 小程序前端:一个输入框、一个按钮、一份回填表单
小程序端页面结构很简单:顶部一个textarea让用户“粘贴或输入收货地址”,中间一个“智能解析”按钮,下面用三个只读 input 展示省市区结果,再加一个详细地址回填框。
关键页面逻辑写在index.js的parseAddress方法里:
// 页面里解析地址的按钮方法 async parseAddress() { const address = this.data.rawAddress; if (!address) { wx.showToast({ title: "先输入或粘贴地址", icon: "none" }); return; } wx.showLoading({ title: "解析中" }); try { // 调云函数,把原始地址传过去 const res = await wx.cloud.callFunction({ name: "parseAddress", data: { address }, }); const r = res.result; if (!r.ok) { wx.showToast({ title: r.msg || "解析失败", icon: "none" }); return; } // 把解析出的结构化成表单数据,交给 wxml 渲染 this.setData({ province: r.data.Province || "", city: r.data.City || "", district: r.data.District || "", detail: r.data.Address || "", }); } catch (err) { // 云函数没部署好或网络异常时,这里能看到具体错误 console.error("地址解析失败", err); wx.showToast({ title: "解析服务异常,稍后再试", icon: "none" }); } finally { wx.hideLoading(); } }逻辑说明:页面先判断本地有没有输入,避免空地址白白消耗一次云函数调用;调wx.cloud.callFunction是云开发的标准调用方式,不需要配域名白名单;拿到云函数返回的result.data之后,把Province、City、District、Address四个字段分别 setData,页面上的表单就自动回填了。
参数说明:name必须和云函数目录名完全一致;data里传的键名要和云函数event.address对应;wx.showLoading和wx.hideLoading成对出现,避免用户在解析过程中反复点击按钮发起重复请求。
3.3 云函数部署的常见顺序与上线前检查
云函数写好后,在开发者工具里右键云函数目录,选择“上传并部署:云端安装依赖”。这一步会帮你在云端执行npm install,本地不用装完再压缩上传。
部署完以后,先在“云开发控制台 → 云函数 → 测试”里直接传一段 JSON 测试事件,比如:
{ "address": "广东省深圳市南山区科技园南区T3栋腾讯大厦" }看返回里有没有正确拆出“广东省 / 深圳市 / 南山区 / T3栋腾讯大厦”。这一步能快速确认密钥、接口名、返回字段三点都通,再回小程序端点按钮。
这里有一个常见的现象:云函数上传后,控制台显示部署成功并不代表代码已经生效到全部可用区。如果小程序端报FunctionNotFound或一直超时,先重新部署一次,再顺手在控制台点一次测试调用冷启动,基本能排除八成问题。
另一个坑是本地测试和云函数测试的差别:本地node index.js直接执行拿不到event.address,因为exports.main = async (event)是给云端运行时调的。要本地验证逻辑,可以在文件末尾加一段if (require.main === module)的测试调用,而不是把线上入口改掉。
4. 地址标准化落地:把解析结果映射成省市区表单并回填格式化的收货地址
4.1 为什么解析出字段还不够,要专门再做一层“标准化格式”
腾讯云 API 返回的是“拆出来的字段”,但业务真正落库和展示时,需要的是“标准化地址串”。这两个概念容易混:解析出来是结构化数据,标准化是让结构化数据能反向拼回一条统一规则的可读地址。
比如同样的地址,有人写“北京市朝阳区望京街10号”,有人写“北京朝阳望京街10号”,还有人写“朝阳区望京街10号,北京”。后两者的解析结果可能完全一致——省=北京市、市=北京市、区=朝阳区、详细地址=望京街10号——但原始文本五花八门。如果直接拿原始文本入库,后面的物流对接、区域统计、重复地址判断全是脏数据。
标准化的常见做法是:以 API 返回的Province + City + District为一级字段,以Address为详细地址,拼接成一条“省市区+详址”的字符串。这块逻辑我会放在前端 setData 之后,一边回填表单一边生成标准化字符串:
// 生成标准化地址字符串,省市区空字段做兜底 formatStandardAddress(province, city, district, detail) { const parts = []; if (province) parts.push(province); // 直辖市场景:city 和 province 相同时不要重复拼接 if (city && city !== province) parts.push(city); if (district) parts.push(district); if (detail) parts.push(detail); return parts.join(""); }逻辑说明:parts数组按省、市、区、详址顺序 push,join("")拼成完整串。注意city !== province这个判断,专门处理直辖市:北京市解析结果里省和市都是“北京市”,如果都拼进去会产生“北京市北京市朝阳区”的冗余地址。
参数说明:detail字段名对应腾讯云返回的Address字段;如果业务上还需要Street或StreetId,可以在拼串前单独判断,不要把空字符串也join进去。格式化后的字符串既用于详情页展示,也作为提交订单时的最终收货地址字段,避免用户改完表单后忘了同步原始输入。
4.2 省市区回填进 picker 的联动逻辑
很多小程序收货地址页用的是picker三级联动,地址解析的价值就在于帮用户跳过“逐级选择”,自动定位到正确的省市区。但 picker 的 value 通常是一组索引,不是省市区名字,需要一个映射过程。
常见做法是:小程序端维护一份行政区划数据,可以拉腾讯云官方的行政区划接口,也可以放一份静态 JSON 按需加载。然后把解析出的省市区名字转成对应索引:
// 假设 areaData 是 [{ name: '北京市', cities: [{ name: '北京市', districts: ['朝阳区', '海淀区'] }] }] resolvePickerIndex(areaData, province, city, district) { let provinceIndex = -1; let cityIndex = -1; let districtIndex = -1; areaData.some((prov, pi) => { if (prov.name === province) { provinceIndex = pi; const cities = prov.cities || []; cities.some((item, ci) => { if (item.name === city) { cityIndex = ci; const districts = item.districts || []; const di = districts.findIndex((d) => d === district); if (di >= 0) districtIndex = di; return true; } return false; }); return true; } return false; }); return [provinceIndex, cityIndex, districtIndex]; }逻辑说明:外层遍历省级,内层遍历市级,最后在区级列表里 findIndex。匹配不到的时候索引保持-1,由调用方决定是让 picker 停在第一项还是弹出提示让用户手动选择。
参数说明:areaData的数据结构必须和 picker 的列数据源保持一致,否则索引对不上;市级名称匹配用全等比较,但解析结果可能带“市”后缀,行政区划数据里的 name 也可能不带“市”。我在实际项目里统一在解析结果返回后先做一个 normalize——去掉省市区后缀再去匹配,这个细节能消掉一大批索引为-1的回退。
4.3 落库前的最后清洗:去掉姓名、电话与冗余空格
腾讯云解析接口的输入如果带“张三 13800138000 北京市朝阳区望京街10号”,解析模型通常能把省市区拆出来,但Address字段里大概率会把电话甚至姓名带进详细地址,导致快递面单打出“张三 13800138000”这种垃圾文本。
我一般在调云函数之前,在云函数侧先做一次清洗:
// 云函数入口内,调用解析接口前先清洗输入 function cleanAddress(raw) { return raw .replace(/1[3-9]\d{9}/g, "") // 去掉大陆手机号 .replace(/[((]?([\u4e00-\u9fa5]{2,4})(先生|女士|小姐|哥哥|姐姐)[))]?/g, "") .replace(/\s+/g, " ") .trim(); }逻辑说明:第一行正则把手机号整体摘掉,1[3-9]\d{9}覆盖目前主流号段;第二行去掉“张先生”“李女士”这类称呼词,长度限制在 2-4 个汉字,避免误伤地址里的“人民大街”之类词组;第三行把多个空格合并成一个,减少模型解析干扰。
参数说明:姓名的正则边界很容易误伤,比如“朝阳区王女士巷”这种恰好包含称呼词的真实地名。所以这个正则只做粗洗,不追求 100% 干净,最终以解析结果的Address字段为准;清洗的意义是降低Address被姓名电话污染的概率。如果发现某个地区的地名频繁被误伤,就把清洗规则收窄或后置校验,而不是无限堆正则。
4.4 字段缺失时的兜底策略:宁可让用户补,不要硬拼数据
解析结果不是每次都能四项齐全。“幸福里小区2栋3单元502”这种地址,模型无法从文本里推断它是哪个区,此时District会为空。如果开发时直接把空字段拼进标准化地址,后端可能把“幸福里小区2栋3单元502”整体当成区名,物流对接直接错乱。
我的兜底策略按优先级排:
Province为空:无解,必须让用户手动选择,这块数据不能编;City为空但Province非空:可以默认填省级直辖,但直辖市除外,仍建议弹窗确认;District为空但省市非空:回填表单时把省市级 picker 定位好,区级字段标注“待确认”,用户补选后提交;Address为空:如果省市都有但缺详址,让用户补门牌号,不要用“不详”占位提交。
这个策略背后的逻辑是:地址数据的错误比缺失更可怕。缺一个区,用户看到表单会主动补;错误行政区划会让订单进错仓库、快递算错运费,这种线上事故比让用户多点一次选择器严重得多。
5. 避坑排查:从签名报错到空区县,4 个真实翻车记录与解法
5.1 翻车一:自己拼 HTTP 请求调腾讯云 API,永远 401 签名错误
现象:拿 Postman 或 Node 原生http模块直接拼请求,按腾讯云官方文档构造 Signature 和 Authorization,调地址解析接口,返回 401 或AuthFailure.SignatureFailure,反复检查参数也对不上。
原因:腾讯云 API v3 用的是 TC3-HMAC-SHA256 签名规范,CanonicalRequest要把 HTTP 请求方法、路径、查询参数、Headers、SignedHeaders 按字典序拼接后做 SHA256。任何一个字段大小写不对、时间戳相差超过 5 分钟、content-type少了; charset=utf-8,签名校验就通不过。这个算法不是照着文档拼就能过,细节非常多,二进制的 SHA256 摘要和十六进制字符串混用是最高频的翻车点。
解决:别自己碰签名,直接用官方 SDK。前面 3.1 里的tencentcloud-sdk-nodejs已经帮你把签名、重试、超时都封装好了。我的血泪经验是,初始化时把secretId/secretKey放进环境变量而不是写在代码里,这样即使代码泄露到仓库,也不会把密钥直接暴露;签名正确性这件事,交给 SDK 后就不要再回头研究手写方案,投入产出比极低。
5.2 翻车二:云函数部署成功,小程序端却报“Function not found”
现象:云函数目录上传部署显示成功,小程序端wx.cloud.callFunction却一直报Function not found,或者偶尔成功偶尔失败。
原因:云函数部署有“上传代码”和“云端安装依赖”两个步骤,如果在同一个函数名上反复上传,控制台可能只更新了代码、没有刷掉旧版本;另一个常见原因是小程序端wx.cloud.init的环境 env 没有指定到云函数所在环境,如果开发者开了多个云环境,callFunction 就会跨环境找不到函数。
解决:在app.js的wx.cloud.init里显式传入环境 ID,可以在云开发控制台首页看到;上传云函数时选择“云端安装依赖”,每次改代码后重新部署。如果还找不到,就在控制台云函数的“测试”选项卡里先跑一次,确认云端能正常响应,再回小程序端排查。查完这两个点,九成 Function not found 都能解决。
5.3 翻车三:地址里的姓名和电话被吞进“详细地址”,面单跟着出错
现象:用户在收货地址页输入“李雷 13712345678 上海市浦东新区世纪大道100号”,点了智能解析后省市区回填正确,但预览生成的标准化地址变成“上海市浦东新区世纪大道100号李雷13712345678”,电话混进了详细地址。
原因:解析模型默认输入就是一段地址文本,它不会先替你做“收货人信息剥离”;姓名和电话不是地址结构的一部分,模型把它识别成地址其余成分,直接合并进Address字段。
解决:严格按 4.3 的清洗方案,在调用解析接口前先执行cleanAddress,把手机号和称呼词去掉再传给 API。如果业务面的收货人信息格式复杂,比如“李雷先生 13712345678 转 801”,可以升级为两步解析:先按空格或“转”拆分,只取包含省市关键词的片段进解析接口。这个坑在测试环境不容易暴露,因为手工测试的地址往往是干净的,只有拿真实用户数据跑回归才会炸出来。
5.4 翻车四:省市区字段拆出来了,但区县字段为空,订单进错仓
现象:一次测试用地址“北京市朝阳区望京西园四区421号楼”,解析后Province和City都正确,District却返回空,或者干脆把“望京西园四区421号楼”整个当成区字段。
原因:一是腾讯云接口对某些社区级地名的行政区划映射不完整,二是模型在没有明确“区”字时保持保守输出,宁可缺字段也不乱猜。这类输入通常是用户漏写了“朝阳区”,只写了“望京西园”,模型就无法回填区级信息。
解决:不在解析层硬补,而是按 4.4 的兜底策略走——区字段为空时,表单回填后把区级 picker 置为“待确认”,让用户顺手补一个。如果订单量大,可以在后端跑一个离线任务:把两周内成功解析且区字段为空的地址收集起来,人工整理一份高频小区到区的映射表,做二次覆盖。注意映射表不要轻易自动生成,宁可人工维护频率低一点,也不要让错误映射固化进标准数据。
6. 批量验证解析效果:用 50 条样本地址守住上线前质量底线
6.1 为什么单测过了不算过:地址解析要跑回归
地址解析这类模型能力,最大的隐患是“单条测试全过,批量测试翻车”。解析结果高度依赖输入写法,用户会写“北京 海淀 中关村大街27号”,也会写“北京市海淀区中关村大街27号(近微软大厦)”,两条输入可能一条成功一条区字段缺失。我一般在上线前会准备一份 50 条左右的样本地址集,覆盖六种类型:标准全称、直辖市、无省无市、带楼栋单元号、带姓名电话、含括号备注。跑批的目的不是看接口稳不稳定,而是看你的清洗规则、标准化拼接、picker 索引映射这整条链路在样本上的整体通过率。
# 批量验证脚本:输入样本 JSON,输出每条解析结果与是否达标 import json import requests with open("samples.json", "r", encoding="utf-8") as f: samples = json.load(f) total = len(samples) pass_count = 0 for item in samples: raw = item["raw"] # 调用你自己的云函数或中转接口,模拟前端链路 resp = requests.post( "https://your-domain.example/api/parse", json={"address": raw}, timeout=5, ).json() data = resp.get("data", {}) # 达标条件:省市区三项非空,且详细地址字段非空 ok = all( [ data.get("Province"), data.get("City"), data.get("District"), data.get("Address"), ] ) if ok: pass_count += 1 else: print(f"[FAIL] {raw} -> {data}") print(f"PASS: {pass_count}/{total}")逻辑说明:脚本按行读取samples.json,每条样本调用中转接口,用“省市区详址四项全部非空”作为达标条件。失败项逐条打印原始输入和返回结果,方便定位是清洗规则的问题还是接口本身的问题。
参数说明:timeout=5是给在线接口的合理阈值,低于 3 秒说明网络或云函数有瓶颈;达标条件可以放宽到“省市非空、区为空但详细地址含门牌号”,这是业务上可接受的下沉容忍度;脚本跑出来的失败项应该逐条手工看,区分“模型拆错”和“清洗规则误伤”,再决定改规则还是改提示。
6.2 上线后留一份解析日志,跑两周再决定要不要调规则
脚本验证解决的是“上线前能不能用”,但真正改变规则的一定是“上线后的真实用户输入”。我习惯在云函数里把原始输入、清洗后输入、解析结果、用户最终提交的地址四段全部写入日志,云开发控制台自带日志查询,也可以推到自己的日志服务。
跑两周后查询三类高频问题:District为空占比、Address含手机号占比、picker 回退到手动选择占比。这三项指标都低于 5% 时,这套地址标准化方案就可以稳定交付;任一指标超过 10%,就回到清洗规则或兜底策略上迭代。
如果用户手动修改了回填后的表单,记录一下最终提交值和解析值的差异,这是最珍贵的规则素材。比如某地区用户经常把“海淀区”补成“北京市海淀区”,说明标准化拼接时直辖市判断还有漏网之鱼;某小区用户频繁把解析出的区字段改成隔壁区,说明行政区划数据有争议。以两周日志为基准迭代一轮,这套“小程序智能识别收货地址 + 腾讯云 API 解析”的链路才算真正收官。我自己每次接这类地址解析需求,都会把日志查询条件预先写好,否则线上翻车的现场只能靠用户截图,查起来真的很玄学。希望帮到你。
本文还有配套的精品资源,点击获取