美国地址标准化原理与实现:从原始输入到规范输出
美国地址标准化原理与实现
地址标准化(Address Standardization)是将用户输入的各种格式地址转换为 USPS 官方标准格式的过程。对于任何需要处理美国地址的系统来说,标准化是确保数据质量的核心环节。
为什么需要地址标准化
实际场景中的问题
用户在表单中输入的地址往往不规范:
| 用户输入 | 标准化后 |
|---|---|
| 123 main street | 123 Main St |
| suite 100, 456 oak avenue | 456 Oak Ave Ste 100 |
| los angeles, ca 90001-1234 | LOS ANGELES, CA 90001-1234 |
| po box 123 | PO Box 123 |
| 789 business boulevard #100 | 789 Business Blvd Ste 100 |
不标准化的后果
标准化核心规则
1. 大写转换
USPS 标准格式要求所有字母大写。
```python
def to_uppercase(text: str) -> str:
return text.upper().strip()
```
2. 街道类型缩写标准化
USPS 官方 street suffix abbreviations 包含 600+ 种类型。
```python
STREET_SUFFIX_ABBREVIATIONS = {
'street': 'ST',
'st': 'ST',
'str': 'ST',
'avenue': 'AVE',
'ave': 'AVE',
'av': 'AVE',
'aven': 'AVE',
'boulevard': 'BLVD',
'blvd': 'BLVD',
'boul': 'BLVD',
'boulv': 'BLVD',
'road': 'RD',
'rd': 'RD',
'drive': 'DR',
'dr': 'DR',
'drv': 'DR',
'lane': 'LN',
'ln': 'LN',
'court': 'CT',
'ct': 'CT',
'way': 'WAY',
'place': 'PL',
'pl': 'PL',
'circle': 'CIR',
'cir': 'CIR',
'crcle': 'CIR',
'highway': 'HWY',
'hwy': 'HWY',
'highwy': 'HWY',
'route': 'RTE',
'rte': 'RTE',
'trail': 'TRL',
'trl': 'TRL',
'parkway': 'PKWY',
'pkwy': 'PKWY',
'expressway': 'EXPY',
'expy': 'EXPY'
}
def standardize_street_suffix(street_name: str) -> str:
"""标准化街道类型后缀"""
words = street_name.split()
if len(words) >= 2:
last_word = words[-1].lower()
if last_word in STREET_SUFFIX_ABBREVIATIONS:
words[-1] = STREET_SUFFIX_ABBREVIATIONS[last_word]
return ' '.join(words)
return street_name
```
3. 方向词缩写
| 全称 | 缩写 |
|---|---|
| NORTH | N |
| SOUTH | S |
| EAST | E |
| WEST | W |
| NORTHEAST | NE |
| NORTHWEST | NW |
| SOUTHEAST | SE |
| SOUTHWEST | SW |
```python
DIRECTIONAL_ABBREVIATIONS = {
'north': 'N', 'n': 'N',
'south': 'S', 's': 'S',
'east': 'E', 'e': 'E',
'west': 'W', 'w': 'W',
'northeast': 'NE', 'ne': 'NE',
'northwest': 'NW', 'nw': 'NW',
'southeast': 'SE', 'se': 'SE',
'southwest': 'SW', 'sw': 'SW'
}
def standardize_directionals(text: str) -> str:
"""标准化方向词"""
words = text.split()
for i, word in enumerate(words):
lower = word.lower()
if lower in DIRECTIONAL_ABBREVIATIONS:
words[i] = DIRECTIONAL_ABBREVIATIONS[lower]
return ' '.join(words)
```
4. 次级地址单元标准化
```python
SECONDARY_UNIT_ABBREVIATIONS = {
'apartment': 'APT',
'apt': 'APT',
'suite': 'STE',
'ste': 'STE',
'unit': 'UNIT',
'floor': 'FL',
'fl': 'FL',
'building': 'BLDG',
'bldg': 'BLDG',
'room': 'RM',
'rm': 'RM',
'department': 'DEPT',
'dept': 'DEPT',
'space': 'SPC',
'spc': 'SPC'
}
def standardize_secondary_unit(address_line2: str) -> str:
"""标准化公寓/套房等次级地址"""
if not address_line2:
return address_line2
words = address_line2.split()
if words:
first_word = words[0].lower()
if first_word in SECONDARY_UNIT_ABBREVIATIONS:
words[0] = SECONDARY_UNIT_ABBREVIATIONS[first_word]
return ' '.join(words)
```
5. 邮编格式标准化
```python
import re
def standardize_zip(zip_code: str) -> str:
"""标准化邮编格式"""
# 移除所有非数字字符
digits = re.sub(r'\D', '', zip_code)
if len(digits) == 5:
return digits
elif len(digits) == 9:
return f"{digits[:5]}-{digits[5:]}"
else:
return zip_code # 无法标准化,返回原值
```
完整标准化流程
```python
class AddressStandardizer:
def __init__(self):
self.suffix_map = STREET_SUFFIX_ABBREVIATIONS
self.directional_map = DIRECTIONAL_ABBREVIATIONS
self.unit_map = SECONDARY_UNIT_ABBREVIATIONS
def standardize(self, address: dict) -> dict:
"""完整地址标准化流程"""
result = address.copy()
# 1. 大写转换
result['address_line1'] = self._to_upper(result.get('address_line1', ''))
result['address_line2'] = self._to_upper(result.get('address_line2', ''))
result['city'] = self._to_upper(result.get('city', ''))
result['state'] = self._to_upper(result.get('state', ''))
# 2. 标准化街道地址
result['address_line1'] = self._standardize_street(result['address_line1'])
# 3. 标准化次级地址
if result.get('address_line2'):
result['address_line2'] = self._standardize_secondary(result['address_line2'])
# 4. 标准化邮编
result['zip'] = standardize_zip(result.get('zip', ''))
# 5. 标准化州缩写
result['state'] = self._standardize_state(result['state'])
return result
def _to_upper(self, text: str) -> str:
return text.strip().upper()
def _standardize_street(self, street: str) -> str:
"""标准化街道名称"""
words = street.split()
# 处理方向词
for i, word in enumerate(words):
lower = word.lower()
if lower in self.directional_map:
words[i] = self.directional_map[lower]
# 处理街道类型后缀
if words:
last = words[-1].lower()
if last in self.suffix_map:
words[-1] = self.suffix_map[last]
return ' '.join(words)
def _standardize_secondary(self, line2: str) -> str:
"""标准化次级地址"""
words = line2.split()
if words:
first = words[0].lower()
if first in self.unit_map:
words[0] = self.unit_map[first]
return ' '.join(words)
def _standardize_state(self, state: str) -> str:
"""标准化州缩写(确保两字母大写)"""
return state.strip()[:2].upper()
```
测试与验证
标准化效果测试
```python
def test_standardization():
standardizer = AddressStandardizer()
test_cases = [
{
'input': {'address_line1': '123 main street', 'city': 'los angeles', 'state': 'ca', 'zip': '90001'},
'expected': {'address_line1': '123 MAIN ST', 'city': 'LOS ANGELES', 'state': 'CA', 'zip': '90001'}
},
{
'input': {'address_line1': '456 north oak avenue', 'address_line2': 'apartment 5b', 'city': 'chicago', 'state': 'illinois', 'zip': '60601'},
'expected': {'address_line1': '456 N OAK AVE', 'address_line2': 'APT 5B', 'city': 'CHICAGO', 'state': 'IL', 'zip': '60601'}
},
{
'input': {'address_line1': '789 business boulevard suite 100', 'city': 'dallas', 'state': 'tx', 'zip': '75201-1234'},
'expected': {'address_line1': '789 BUSINESS BLVD', 'address_line2': 'STE 100', 'city': 'DALLAS', 'state': 'TX', 'zip': '75201-1234'}
}
]
for i, case in enumerate(test_cases):
result = standardizer.standardize(case['input'])
print(f"测试 {i+1}: {'通过' if result == case['expected'] else '失败'}")
print(f" 输入: {case['input']}")
print(f" 输出: {result}")
print()
test_standardization()
```
与 USPS API 结合使用
```python
class AdvancedAddressProcessor:
def __init__(self):
self.local_standardizer = AddressStandardizer()
self.usps_client = USPSAPIClient()
async def process(self, raw_address: dict) -> dict:
"""本地标准化 + USPS API 验证"""
# 第一步:本地标准化
standardized = self.local_standardizer.standardize(raw_address)
# 第二步:USPS API 验证和补全
try:
verified = await self.usps_client.verify(standardized)
return {
'status': 'verified',
'address': verified,
'confidence': 'high'
}
except USPSAPIError:
# USPS 失败时返回本地标准化结果
return {
'status': 'standardized_only',
'address': standardized,
'confidence': 'medium',
'note': 'USPS API 不可用,仅进行了本地标准化'
}
```
总结
地址标准化是一个多步骤的文本处理流程,核心包括:
通过建立完整的标准化规则库,你的系统可以将任意格式的用户输入转换为 USPS 标准格式,大幅提升地址数据的质量和一致性。