在处理跨国业务数据、用户注册信息或物流系统时,国家字段往往以不同形式出现:有的是英文全称,有的是二位代码,还有中文简称。要把这些数据统一成标准的ISO代码,单纯靠人工对照很容易遗漏。Python生态里有现成工具可以完成这件事,本文会从最常用的pycountry库讲起,逐步介绍名称匹配、批量转换以及一些容易踩坑的细节。

之所以推荐pycountry,是因为它内置了ISO 3166-1标准中的国家与地区数据,同时提供简单的属性访问方式,几分钟内就能完成单个查询和批量处理。下面的示例会覆盖英文名称、二位代码、三位代码之间的互相转换,以及处理中文名称时需要额外维护映射的情况。
使用pycountry库进行基础查询
pycountry是Python中处理ISO国家、语言、货币等标准代码的常用库,安装非常简单,执行pip install pycountry即可。它内置了ISO 3166-1国家列表,每个国家对象都有name、alpha_2、alpha_3和numeric等属性。其中alpha_2是二位字母代码,例如CN、US;alpha_3是三位字母代码,例如CHN、USA;numeric则是三位数字代码。
通过countries.get()方法可以进行精确查询,传入的参数可以是名称、二位代码、三位代码或数字代码。下面是一个完整的示例,展示了如何用英文名称查询中国,再反向通过代码获取国家名称。
import pycountry # 通过英文名称查询 country = pycountry.countries.get(name='China') print(country.alpha_2) # CN print(country.alpha_3) # CHN print(country.numeric) # 156 # 通过二位代码反向查询 cn = pycountry.countries.get(alpha_2='CN') print(cn.name) # China # 通过三位代码查询 jp = pycountry.countries.get(alpha_3='JPN') print(jp.name) # Japan print(jp.alpha_2) # JP
实际运行时可以看到,属性访问非常直观,拿到国家对象后直接读取对应字段即可。需要注意的是,countries.get()在找不到结果时会返回None,而不会抛出异常,因此如果需要严格校验,可以额外判断返回值。如果希望使用更宽松的模糊匹配,可以使用lookup()方法,它能够识别部分名称和官方代码,但遇到非常用缩写时仍然会抛出LookupError,所以建议封装一层异常处理。
下面是一个安全查询函数,它在查询失败时返回None,避免中断整个数据处理流程。
import pycountry
def safe_lookup(query):
try:
return pycountry.countries.lookup(query)
except LookupError:
return None
print(safe_lookup('United States').alpha_2) # US
print(safe_lookup('Germany').alpha_3) # DEU
print(safe_lookup('not a country')) # None
需要留意的是,lookup()虽然比get()灵活,但它主要针对ISO标准名称和代码,不能自动把USA、UK这类常见口语缩写映射到US和GB。这类变体问题会在下一节专门讨论。
处理国家名称变体与中文映射
真实数据里的国家字段往往不规范,可能存在USA、U.S.A.、United States of America、美国等多种写法。pycountry默认数据并不会包含这些别名,直接查询可能返回None或抛出异常。解决思路是维护一个自定义映射表,将常见变体手动对应到ISO二位代码。这个映射表可以放在Python字典中,也可以保存为JSON或CSV文件方便长期维护。
下面的代码先建立了一个包含英文变体和中文名称的映射字典,然后在查询函数中优先检查这个字典,未命中时再回退到pycountry的模糊查询。
import pycountry
# 常见名称变体映射,键为输入中可能出现的写法,值为ISO二位代码
alias_map = {
'USA': 'US',
'U.S.A.': 'US',
'United States of America': 'US',
'UK': 'GB',
'United Kingdom': 'GB',
'South Korea': 'KR',
'Korea, South': 'KR',
'Russia': 'RU',
'Vietnam': 'VN',
'中国': 'CN',
'美国': 'US',
'英国': 'GB',
'德国': 'DE',
'日本': 'JP',
'法国': 'FR',
}
def get_iso_code(name):
# 先检查别名映射
if name in alias_map:
return alias_map[name]
# 再尝试pycountry精确或模糊查询
try:
country = pycountry.countries.lookup(name)
return country.alpha_2
except LookupError:
return None
print(get_iso_code('USA')) # US
print(get_iso_code('中国')) # CN
print(get_iso_code('Germany')) # DE
这种先别名后标准查询的方式兼顾了灵活性和准确性。对于中文名称,如果业务数据基本固定,可以直接在映射字典中覆盖常用国家;如果数据来源较多,建议把映射内容迁移到外部文件,启动时加载一次,避免每次修改代码。例如将别名表保存为alias_map.json,然后用json.load()读取;这样做的好处是后续新增别名无需改动核心逻辑。
还可以考虑使用一些开源的中文国家名称映射包或者从维基百科数据生成映射表,但这些方案的维护成本和依赖复杂度较高,对于大多数项目来说,一个几十项的字典已经足够。唯一要注意的是保持映射值与ISO 3166-1的二位代码完全一致,否则下游系统可能出现数据不一致。
使用pandas批量转换数据列
单条查询适合接口调用或少量校验场景,而数据分析中最常见的需求是对整列国家名称进行转换。pandas提供的apply()方法可以很方便地配合前面的查询函数完成批量处理。假设有一个DataFrame,其中的country列包含多种写法,目标是在右侧新增一列标准ISO代码。
import pandas as pd
import pycountry
df = pd.DataFrame({
'country': ['China', 'United States', 'Germany', 'Japan', 'Unknown']
})
def to_iso(name):
try:
return pycountry.countries.lookup(name).alpha_2
except LookupError:
return None
df['iso_code'] = df['country'].apply(to_iso)
print(df)
运行这段代码后,iso_code列会分别显示CN、US、DE、JP和None。对于未匹配到的国家,返回None可以保留在DataFrame中,后续可以通过df[df['iso_code'].isna()]筛选出未识别的记录,从而有针对性补充别名映射。这种处理方式比直接抛异常更适合实际数据清洗流程。
如果数据量较大,例如几十万行,重复的字符串查询会导致性能下降。此时可以使用functools.lru_cache给查询函数加上缓存,把已经查过的名称结果保存在内存中。由于国家名称的去重数量通常不多,缓存命中率会非常高。
from functools import lru_cache
import pycountry
@lru_cache(maxsize=128)
def to_iso_cached(name):
try:
return pycountry.countries.lookup(name).alpha_2
except LookupError:
return None
# 假设df是包含country列的大型DataFrame
# df['iso_code'] = df['country'].apply(to_iso_cached)
需要注意lru_cache要求传入的参数是可哈希的,这里的国家名称是字符串,满足条件。如果传入的值可能为None或非字符串类型,建议在函数内部先做类型判断,避免缓存到无效数据。另外,如果映射表会动态更新,缓存可能导致旧值残留,这种情况下可以手动清除缓存或放弃缓存方案。
ISO 3166-1与3166-2的区别及异常处理
在使用国家代码时,经常有人把ISO 3166-1和ISO 3166-2混为一谈。ISO 3166-1定义的是国家或地区级别的代码,也就是本文一直在讨论的CN、US、DE这类二位代码;ISO 3166-2则是在此基础上细分出的省级或州级行政区代码,例如中国的北京市对应CN-BJ,上海市对应CN-SH。pycountry同样提供了subdivisions模块来查询这些下级行政区域。
如果业务中需要记录用户所在的省份或州,可以使用下面的方式获取某个国家的全部行政区划。
import pycountry
# 查询中国的省级行政区代码(ISO 3166-2)
subdivisions = pycountry.subdivisions.get(country_code='CN')
for sub in subdivisions[:5]:
print(sub.code, sub.name)
这段代码会输出前五个中国省份的ISO 3166-2代码和名称,例如CN-BJ 北京市、CN-TJ 天津市等。需要注意的是,subdivisions.get()的参数是country_code,而不是国家名称。如果只知道国家名称,需要先用国家查询得到alpha_2,再传给行政区查询。
异常处理方面,使用lookup()方法时如果查询不到结果会抛出LookupError,而get()返回None。因此建议在任何用户输入或外部数据进入查询函数前,都统一使用try/except包裹,并返回可识别的默认值。这样即使数据集中出现拼写错误、空值或未收录的地区名,也不会导致整个批量任务中断。同时可以在日志中记录未匹配的原始值,方便后续补充别名表。
总的来说,Python查询国家ISO代码的核心思路是:用pycountry处理标准名称,用自定义映射弥补变体和中文名称,用pandas和缓存提升批量处理效率。理解ISO 3166-1与3166-2的区别后,就能根据实际业务选择正确的代码类型,避免在数据报表或接口字段中产生混淆。