中文快递地址解析实战:规则引擎与序列标注融合抽取六字段

发布时间:2026/10/19 14:54:03
中文快递地址解析实战:规则引擎与序列标注融合抽取六字段
做快递、电商、CRM这一类系统的人一定绕不开一个看起来不起眼、实际上特别磨人的问题用户填的收货地址永远是乱七八糟的。我第一次认真面对快递地址解析这个需求是因为客服后台每天收到几百条地址无法识别的工单人工核对一遍要花几个小时还经常被用户催单骂。后来我们把这六个字段——省、市、区、详细地址、收件人、电话——的自动抽取真正落地了才把这件事从体力活变成技术活。这篇文章就把我在实际项目里做快递地址解析的完整拆解过程写出来规则方案怎么做、模型怎么兜底、脏数据怎么治理、验收口径怎么定以及那些踩过才知道的坑。给正在做或者准备做地址解析的读者一条可以直接参考的路线图。1. 快递地址解析拆的是什么难在哪1.1 一段典型的脏地址长什么样先看几条真实系统里截出来的原始输入感受一下问题张三13812345678广东省深圳市南山区科技园南区某栋某室 李四 收货地址北京市海淀区某大学7号宿舍楼 152******** 王五收湖北省武汉市洪山区珞喻路某号 手机 13855667788 某公司市场部 刘经理 021-88886666 上海徐汇漕河泾开发区某路某弄某号这几条地址的共同特点是所有信息挤在一个字符串里没有统一分隔符地名、人名、电话号码交错出现。有的人把电话放最前面有的人把收件人放中间还有人在详细地址里写了请放在前台这种备注。快递地址解析要做的就是从这种自由文本里准确地抽出结构化字段落到订单系统里。1.2 难点的本质没有分隔符层级还交错如果你觉得这事儿就是用正则抠个电话、按字典匹配省市那大概率会低估它。我梳理下来真正的难点集中在四类层级缺失用户只写深圳南山区科技园省和市的信息混在一起用户只写海淀区xxx路前面没有北京市。地区名称歧义上地可能是地名也可能是人名朝阳可能是人名也可能是区名三河可能是河北的三河市也可能是某个小区名。规范与不规范的混用有人写上海市浦东新区有人写上海浦东还有人写沪浦东新区幸福路。干扰信息地址里混了请在12点前送到放驿站转交某位老师这类备注还有座机号、分机号、两个手机号。如果把这些症状总结成一句话就是中文地址是一种高度依赖上下文、又没有显式分隔符的短文本。解析方案的设计本质上就是在跟上下文歧义和录入自由作斗争。1.3 输出的标准结构六个字段整个系统对内对外都围绕一个统一标准结构province、city、district、detail、name、phone外加一个remark用于存放放驿站转交前台这类衍生信息。有些业务还要postcode、landmark但核心就是前六个。我建议一开始就别贪多先把六字段做扎实了后面加字段是增量改动。2. 第一版方案词典加正则先把能拆的拆干净2.1 行政区域词典与高效匹配地址解析的地基是行政区划词典。我们当时用的是公开的行政区划代码数据整理成三级结构省/自治区/直辖市、地级市/地区/自治州、区/县/县级市。这里有个细节容易忽略词典不只是存标准名还要存别名和简称。举几个我们实际维护过的别名标准名别名/简写内蒙古自治区内蒙古、内蒙新疆维吾尔自治区新疆西藏自治区西藏广西壮族自治区广西北京市北京、京上海市上海、沪深圳市深圳、鹏城慎用海南省海南、琼别名表的维护是个长期活但它能极大提升召回率。匹配时如果每次都对全量词典做线性扫描几万条目会拖慢速度。我们用了Trie树做前缀匹配先把省市区三级行政区划全部塞进一棵Trie每个节点标记当前路径对应的行政级别和代码解析时从地址串头部开始扫描能快速切出最长匹配的省市区名称。2.2 电话号码最没有悬念的一步在所有字段里电话号码是最容易抽的因为它的模式足够稳定。我用的正则大致是这样import re # 手机号1开头第二位3-9共11位 MOBILE_PATTERN re.compile(r(?!\d)(\?86[- ]?)?1[3-9]\d{9}(?!\d)) # 座机区号3-4位号码7-8位可能有分机 LANDLINE_PATTERN re.compile(r(?!\d)(0\d{2,3})[- ]?(\d{7,8})([- ]?(?:\d{1,6}))?(?!\d)) def extract_phone(text): text re.sub(r[(]?转[)]?\s*\d{1,6}, lambda m: , text) mobiles MOBILE_PATTERN.findall(text) # 进一步处理 86、空格、横线等 return mobiles这个正则看起来简单但有几个经验点一定要加(?!\d)和(?!\d)这种边界断言否则地址里的连续数字会跟手机号粘连。处理座机时分机号是个大坑。用户写021-88888888转8012如果不管分机后面的8012会被当成门牌号或邮编。我上面先用一个替换把转xxx摘出去存进临时变量再做解析。如果同一串里出现两个手机号业务上要把它标成异常订单因为正常情况只该有一个收件人电话。这也是一种数据质量信号。2.3 省市区逐级剥离与合并提取完电话之后剩下的字符串里还混着省市区和详细地址、收件人。我们的第一步策略是从左往右逐级剥离在剩余字符串开头匹配省/自治区/直辖市名称命中后从原串里切掉。在剩余字符串开头匹配地级市/地区名称。这里有个合并逻辑如果第一步已经命中了直辖市比如北京市海淀区那么市这一级就落在北京市本身接下来要直接找区。匹配区/县/县级市名称命中后切掉剩下部分就是详细地址。这个过程看起来线性实际上到处是分支。最典型的两个问题问题一用户不写省只写深圳市南山区怎么办我们的做法是维护一张市级单位 - 省份的反查表从地址里先找到市级单位就能把省补出来。注意这里优先匹配城市不是优先匹配区因为同名的区很多比如浦东新区在多个省都可能存在虽然实际只有上海有但开发区高新区这类名字更需要防误判。问题二朝阳区和朝阳这种区名带姓氏的情况。我见过不少姓朝阳的真实用户。规则方案里我会优先在去掉姓名候选之后的字符串里找区名而不是上来就匹配。怎么找姓名候选见下一节。2.4 收件人的抽取看起来简单实际最啰嗦收件人名字的抽取本质上是在地址已经尽量剥干净的剩余文本里找一段2到4个汉字的人名。难点在于人名没有统一的语言特征跟区名、路名、小区名都可能重叠。我们当时的规则组合是这样的先处理显式标记收件人收姓名这类前缀或者收这样的后缀直接截取。如果名字出现在地址前面或后面且长度在2-4个汉字之间且不跟省市区词典、常用路名后缀路、街、道、巷、弄、里、苑、园、城、厦、场冲突就按姓名收入。遇到某公司市场部 刘经理这种公司名加人名混在一起的情况我们设置了白名单头衔词典经理、先生、女士、小姐、老师、女士、前台、站长。用头衔倒推前面的人名边界。要注意的是规则抽姓名一定会有漏所以我在整体方案里给姓名留了最大的容错空间——解析结果里姓名置信度低于阈值时走人工复核队列这个策略后面会细说。3. 规则撑不住的地方用序列标注来兜底3.1 规则方案的三个死穴第一条路跑通之后我在线上抽样了5000条订单发现规则方案在三条典型场景上表现很差省略和倒装深圳南山区科技园里深圳和南山区之间少了市逐级剥离时城市匹配直接失败。新区和新地名某些新成立的开发区、自贸区、高新区没有及时进词典整条地址的切分顺序被打乱后面的全跟着错。地名和人名冲突李朝阳朝阳区朝阳路这种哪个朝阳是人都分不清规则一匹配区名就把人名吃掉了。这些问题靠加规则也能修但每修一个场景就会冒出新场景永远在打补丁。于是我们决定上序列标注模型把分词和命名实体识别一起解决。3.2 把地址解析当序列标注问题来做序列标注的思路很直观给每个字符打一个标签比如B-PROV省开始、I-PROV省内部、B-CITY、I-CITY、B-DIST、I-DIST、B-DETAIL、I-DETAIL、B-NAME、I-NAME、B-PHONE、I-PHONE、O其他。我一开始用的是字符级特征加条件随机场后来换成双向LSTM再加CRF解码层。字符级的好处是不需要分词器避免了分词错误传导到实体识别的问题。特征上除了字符本身还拼了以下几类字符位置特征是否是名字常见姓氏赵钱孙李这种是否数字是否字母。词典特征当前字符往前最长能匹配到的省/市/区名称做一个match_level和match_length的标记。这一步很像把一个规则匹配的结果当成特征喂给模型。训练数据来源是两个一是之前规则方案已经解析对的线上数据做去重和清洗后当成弱标注二是人工标注了1万多条困难样本就是规则解错的那类。弱标注可以用但必须过滤否则会把规则的错误当正确答案学进去。3.3 规则和模型的融合方式置信度分流模型上了之后我没有直接让它替代规则而是做了个两阶段管线规则优先先用正则和词典把电话抽出来把确定性的省市区切掉。模型兜底对规则解析置信度不高、或字段不完整的地址交给序列标注模型重新打标。合并裁决两边的输出如果一致直接采纳不一致时看置信度高置信度的一方取胜都不高则进人工队列。这个设计的好处是规则方案在电话、地名匹配上仍然可靠模型负责处理灵活性更高的语义片段两者各管一段。我实测下来的体感是准确的规则加模型兜底比单一方案在全部样本上的准确率高大概三到五个百分点而且更容易查错——规则错的就是词典缺失模型错的就是样本分布。4. 字典、纠错与地址补全脏数据治理三板斧4.1 缺省字段怎么补宁可标记不要瞎猜地址里经常缺省、缺市、缺区。比如武汉市洪山区有市有区没省西湖区文三路只有区。我见过一些团队为了让字段看起来完整直接用出现频率最高的省份去填这在我看来是灾难性决策。一个地址如果本身没写省你填一个错省后面的物流路由、运费模板全都会跟着错。正确的顺序是如果只有区没有省市先看区归属哪个市再看市归属哪个省从下往上用行政区划关系表补全。如果区名在市表里查不到常见原因是这个区是个新区或功能区这时候不补标记为district_guessfalse。补全结果必须带confidence字段下游系统可以据此决定是直接使用还是人工确认。我特别想强调对地址解析系统来说我不确定是一种有效的输出状态。敢于标记低置信度比硬着头皮给一个结果更负责任。4.2 错别字与同音字归一化用户手打地址错别字是常态。海淀区打成海定区乌鲁木齐打成鸟鲁木齐亭湖区打成停湖区。这一类问题纯词典匹配基本必挂我用的方法分两层拼音模糊匹配把省市区名称转成拼音再对输入片段转拼音用编辑距离做候选召回。比如海定和海淀的拼音haiding/haidian编辑距离只有1很容易召回。字形相似候选对常见别字维护一个易错字映射表。比如定-淀洲-州陵-凌塘-唐。这个表不大但命中率极高因为用户的错字其实是高度集中的。归一化要在切分之前做。我们的实际流程是先把原始串做一次别名归一化包括繁体转简体得到一个规范化的中间串所有后续的匹配和模型推断都基于这个中间串。模型训练时也用归一化后的数据不然模型会学到一堆错别字的特例泛化很差。4.3 备注和干扰信息的剥离地址里的备注信息是最容易被忽略的脏数据源。常见的有三类配送要求请放在东门快递柜放前台谢谢五点后送转交信息转交李老师给三楼王女士无关字符不接电话公司上班时间我们的处理方式是把它们摘到remark字段里而不是直接丢掉——因为放驿站、代签收这些信息对末端配送是有用的。识别方法先用规则抓关键词请、放、转、到、勿、代收再用模型补充备注的识别不要求太精准漏掉一两条不影响主流程但千万别把备注里出现的省市区名误当成地址主体。5. 验收口径准确率不是拍脑袋定的5.1 字段级准确率与记录级准确率做技术方案时没人问你感觉准不准都会问准确率多少。我踩过的坑是第一版只报了一个整体准确率93%结果业务方一问到底是哪个字段不准就答不上来。后来我固定了两个口径字段级准确率省、市、区、详细地址、姓名、电话每个字段单独统计。计算公式是该字段解析正确的样本数 / 样本总数。记录级准确率六个字段全部正确才算一条样本正确。记录级准确率 完全正确的记录数 / 总记录数。这两个口径差异巨大。字段级都是95%以上的时候记录级可能只有75%。原因是每个字段的3%误差叠加起来完全正确的概率就降得很厉害。所以对外报准确率必须先说清是哪个口径。5.2 测试集的构建与回归机制为了不让准确率沦为自嗨测试集必须独立于开发样本。我们的做法是每月从线上随机抽3000条订单剔除敏感信息后做人工标注。人工标注采用双人标注不一致的样本由第三人裁决保证标注质量。每周跑一次回归用最新的代码和模型在历史测试集上跑一遍看准确率有没有回退。回归触发回退最常见的原因就是词典更新——新加的区名覆盖了老的自定义别名。所以我在CI里加了一条规则新增行政区划词条时必须跑一遍全量测试集防止修一个区、坏一片地址。另外一个经验是灰度期的影子模式。新版本上线前会把线上流量的地址复制一份让新旧两套解析并行跑但结果只记录不生效。跑一周之后对比两个版本的分字段准确率再决定是否全量切换。影子模式不用改造上游系统只加一个旁路日志成本很低但能避免很多线上事故。6. 落地过程中的几个值得记住的坑6.1 数字在地址里的歧义用户填科技园188号里面那串连续数字可能被正则误认成座机号。青岛市南区香港中路110号里也有连续数字。我的防护措施是数字连续长度超过5位且电话号码正则匹配后再去掉区号规则再验证一遍。也就是说数字串要既满足电话模式又满足正常座机区号结构才算电话否则宁可把它留给详细地址。6.2 小区名、楼盘名里的市和区阳光市花园东安区公寓这类名称是词典匹配的大坑。我之前遇到过一条地址深圳市龙岗区大鹏新区月亮湾市花园3单元规则把市花园里的市当成市级单位直接切错。处理办法是市级匹配时要看匹配片段后面跟的是区/县还是普通名词如果是市花园市公寓这类组合就把市的回溯长度降级不强行匹配。6.3 性能问题Trie的内存与长尾词典膨胀后Trie的内存和匹配速度会互相拉扯。我们维护了一套热词缓存在普通词典Trie之上用LRU缓存最近一周高频命中的省市区词条和别名命中缓存时跳过完整Trie查询。实测吞吐量从每秒几百条涨到每秒几千条。如果你的系统一天只有几万单其实不需要做这个优化但如果要做实时联动、校验多地址就得考虑。6.4 要不要做到100%人工队列的位置我在这个项目里最大的认知转变是明白了解析系统天生不是一个追求100%的系统。因为它输入是自由的用户文本注定有一部分是当前方案解不了的。与其把所有精力砸在最难的1%上不如把人力入口做顺低置信度地址自动埋单进人工核对队列客服在界面上一键修正修正结果回流作为训练样本。这个闭环让系统越用越准而且把解析错误的体验从用户收货失败降级成客服看一眼价值是完全不一样的。6.5 线上数据回流让系统自己变聪明最后再分享一个我坚持了很久的做法所有人工修正的地址都要打点记录。修正前、修正后、修正人、时间戳全部结构化存下来。这些数据既是模型的增量训练集也是规则词典的更新线索。我们每两周做一次修正汇聚分析看看人工改得最多的字段和地区是哪些然后对应更新词典、别名和纠错表。这个过程跑了一段时间之后系统的人工介入率从最初的百分之十几降到了百分之三左右。做地址解析永远不要觉得规则写完了就结束了它不是一次性开发而是一套需要持续运营的数据流水线。