这是快递分拣工具,输入地址按设定的规则自动匹配到对应的配送站点。
```python
# -*- coding: utf-8 -*-
"""
快递按收货地址自动分拣
核心思路:每条规则描述一个站点负责的范围(省/市/区 + 关键词),
分拣时对所有规则打分,取「优先级最高、匹配最精确」的那条。
"""
import re
from collections import defaultdict
from dataclasses import dataclass
from typing import Iterable, List, Optional, Sequence, Tuple
# ---------------- 1. 地址归一化 ----------------
_FULL_TO_HALF = str.maketrans(
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz",
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz",
)
def normalize(address: str) -> str:
"""全角转半角 + 去掉空白和常见分隔符,
让「北京市 朝阳区」「北京市,朝阳区」和「北京市朝阳区」等价。"""
if not address:
return ""
s = address.translate(_FULL_TO_HALF)
return re.sub(r"[\s,,.。;;、\-—_/\\|()()【】\[\]]+", "", s)
# ---------------- 2. 分拣规则 ----------------
@dataclass
class Rule:
"""一条分拣规则:某个站点负责的地址范围"""
site: str # 目标配送站 / 分拨中心
province: str = ""
city: str = ""
district: str = ""
keywords: Tuple[str, ...] = () # 街道、乡镇、园区等
priority: int = 0 # 越大越优先,用于强制覆盖
def __post_init__(self):
# 规则里的地址同样要归一化,保证和收件地址"同一套写法"
self.province = normalize(self.province)
self.city = normalize(self.city)
self.district = normalize(self.district)
self.keywords = tuple(normalize(k) for k in self.keywords if k)
def match_score(self, addr: str) -> Optional[int]:
"""addr 必须已归一化。命中返回得分(越高越精确),不命中返回 None。
打分逻辑:
- 规则里填写的省 / 市 / 区,必须全部出现在地址中(硬约束);
- 命中的层级越多,分数越高(省 100、市 50、区 20);
- 关键词每命中一个 +5,用于同区内再细分。
"""
if not any((self.province, self.city, self.district, self.keywords)):
return None
score = 0
for value, weight in ((self.province, 100), (self.city, 50), (self.district, 20)):
if value:
if value not in addr:
return None
score += weight
for kw in self.keywords:
if kw in addr:
score += 5
return score or None
# ---------------- 3. 分拣器 ----------------
@dataclass
class SortResult:
address: str
site: str
matched: bool
rule: Optional[Rule] = None
class AddressSorter:
def __init__(self, rules: Sequence[Rule], default_site: str = "人工分拣区"):
self.rules = list(rules)
self.default_site = default_site
def sort(self, address: str) -> SortResult:
addr = normalize(address)
best_rule, best_key = None, None
for rule in self.rules:
score = rule.match_score(addr)
if score is None:
continue
key = (rule.priority, score) # 先比优先级,再比精确度
if best_key is None or key > best_key: # 同分时先注册的规则胜出
best_rule, best_key = rule, key
if best_rule is None:
return SortResult(address, self.default_site, False, None)
return SortResult(address, best_rule.site, True, best_rule)
def sort_batch(self, addresses: Iterable[str]) -> List[SortResult]:
return [self.sort(a) for a in addresses]
# ---------------- 4. 规则配置 + 使用示例 ----------------
RULES: List[Rule] = [
# 北京:朝阳区内再细分出「望京站」
Rule(site="北京-望京站", province="北京", city="北京", district="朝阳区",
keywords=("望京", "酒仙桥", "大山子", "阜通", "来广营")),
Rule(site="北京-朝阳站", province="北京", city="北京", district="朝阳区"),
Rule(site="北京-海淀站", province="北京", city="北京", district="海淀区"),
# 上海
Rule(site="上海-虹桥站", province="上海", city="上海", district="闵行区",
keywords=("虹桥", "华漕", "七宝")),
Rule(site="上海-浦东站", province="上海", city="上海", district="浦东新区"),
# 广东
Rule(site="广州-天河站", province="广东", city="广州", district="天河区"),
Rule(site="深圳-南山站", province="广东", city="深圳", district="南山区"),
# 注意:全国有两个「朝阳」——靠省市区三级约束区分
Rule(site="辽宁-朝阳站", province="辽宁", city="朝阳", district="双塔区"),
]
if __name__ == "__main__":
sorter = AddressSorter(RULES, default_site="北京-人工分拣")
addresses = [
"北京市朝阳区望京街道阜通东大街6号",
"北京市朝阳区建国路88号SOHO现代城",
"北京市海淀区中关村大街1号",
"上海市闵行区虹桥火车站出发层",
"上海市浦东新区世纪大道100号",
"广东省广州市天河区体育西路103号",
"广东省深圳市南山区科技园南区",
"辽宁省朝阳市双塔区新华路1号",
"火星基地一号舱", # 匹配不到 → 兜底
]
results = sorter.sort_batch(addresses)
print("=== 逐单分拣结果 ===")
for r in results:
flag = "✔" if r.matched else "✘"
print(f"{flag} {r.address} -> {r.site}")
print("\n=== 按站点汇总(实际装车用)===")
groups = defaultdict(list)
for r in results:
groups[r.site].append(r.address)
for site, addrs in groups.items():
print(f"\n【{site}】共 {len(addrs)} 单")
for a in addrs:
print(f" - {a}")
```
分拣逻辑与操作说明
这段代码就像一个智能分拣员,收到地址后按您定义的规则自动匹配站点,并给出最终去向。
· 地址归一化:先把全角字符、空格和标点统一成半角并去掉分隔符,避免“北京市 朝阳区”和“北京市朝阳区”被当成不同地址。
· 规则匹配:每条规则包含省、市、区、关键词和优先级;省市区必须全部命中才有效,命中的层级越多得分越高,关键词每命中一个再加分。
· 判定与兜底:所有规则中得分最高且优先级最高的胜出;如果没有任何规则命中,自动归入“人工分拣区”。
· 结果展示:逐单显示匹配结果(✔或✘),并按站点汇总每个配送站需要装车的订单。
---
优化建议: 规则配置集中在代码末尾的 RULES 列表中,可以按同样的格式增删站点和区域;批量测试地址就在下面的 addresses 列表里修改。
文章仅供参考用。