文章目录
- 环境信息
- 前言:正则能匹配"格式",但匹配不了"真假"
- 一、香港身份证号码的格式:三层结构
- 二、校验码算法:mod 11 加权
- 三、香港车牌格式:传统 vs 自定义
- 四、香港电话号码格式
- 五、完整验证器(可直接复用)
- 六、三个易错点
- 坑1:全角括号
- 坑2:字母转数字的偏移量
- 坑3:逻辑有效 ≠ 真实存在
- 七、总结
环境信息
| 项目 | 版本/说明 |
|---|---|
| Python | 3.10+ |
| 标准库 | re(正则)、无需第三方依赖 |
| 验证对象 | 香港身份证 / 车牌 / 电话号码 |
| 数据来源 | 格式规则和校验码算法为公开标准 |
前言:正则能匹配"格式",但匹配不了"真假"
上周我在做一个表单数据清洗的活,需要校验用户填写的香港身份证号码。我第一反应是写个正则表达式:
importre pattern=r'^[A-Z]{1,2}\d{6}\([0-9A]\)$'格式上是匹配了——1到2个大写字母 + 6位数字 + 括号里的校验码。但很快我就发现一个问题:A123456(3)和A123456(9)都符合这个正则,但只有一个是真的。
正则表达式只能验证"格式对不对",验证不了"号码是不是真实存在的"。香港身份证号码里那个括号中的校验码,不是随便写的——它是通过一个 mod 11 加权算法算出来的。格式匹配只是第一层,校验码验证才是第二层。
这篇文章讲的就是这"两层验证"。前半部分是正则解析香港身份证、车牌、电话的格式,后半部分是香港身份证校验码算法的完整实现。
收藏提示①:正则匹配"像不像",校验码算法验证"是不是真的"。表单验证、数据清洗、反作弊,缺一层都不行。文末有完整可复用的验证器代码。
一、香港身份证号码的格式:三层结构
香港身份证号码由三部分组成(以A123456(3)为例):
A 123456 (3) ↑ ↑ ↑ 字母 6位数字 校验码(0-9或A)规则:
- 开头是 1-2 个大写英文字母(A-Z)
- 中间是 6 位数字
- 括号里是校验码,0-9 或字母 A
- 括号可能是半角
()或全角()
对应的正则:
importre# 支持半角/全角括号,校验码0-9或AHKID_PATTERN=re.compile(r'^([A-Z]{1,2})(\d{6})[((]([0-9A])[))]$')defparse_hkid(hkid:str):"""解析香港身份证号码,返回(字母部分, 数字部分, 校验码)"""m=HKID_PATTERN.match(hkid.strip().upper())ifnotm:returnNonereturnm.group(1),m.group(2),m.group(3)# 测试print(parse_hkid('A123456(3)'))# ('A', '123456', '3')print(parse_hkid('AB987654(3)'))# 全角括号也能识别print(parse_hkid('A1234563'))# 无括号 → None(不匹配)注意一个细节:我用了[((]和[))]来同时匹配半角和全角括号——香港用户在中文输入法下输入时,经常打出全角括号,只匹配半角会漏掉一大批真实数据。
二、校验码算法:mod 11 加权
这是本文的核心。香港身份证的校验码算法是公开的,逻辑如下:
- 字母转数字:A=10, B=11, C=12, …, Z=35
- 单字母补空格:如果只有一个开头字母,前面补一个"空格",值=36
- 加权相乘:从右到左,权重依次是 9,8,7,6,5,4,3,2,1(校验码位权重是1)
- 求和取余:所有乘积相加,除以 11 取余数
- 校验码规则:
- 余数 = 0 → 校验码是 0
- 余数 = 1 → 校验码是 A
- 余数 = 2~10 → 校验码 = 11 - 余数
用A123456(3)举例验证:
A=10,单字母前面补空格=36 加权:36×9 + 10×8 + 1×7 + 2×6 + 3×5 + 4×4 + 5×3 + 6×2 = 324 + 80 + 7 + 12 + 15 + 16 + 15 + 12 = 481 481 ÷ 11 = 43 余 8 11 - 8 = 3 → 校验码 = 3 ✓完整实现:
defcalculate_hkid_check_digit(letters:str,digits:str)->str:""" 计算香港身份证号码的校验码 letters: 开头字母部分(1-2个大写字母) digits: 6位数字字符串 返回: 校验码('0'-'9' 或 'A') """# 1. 构建完整的8位"值序列"(不含校验码位)# 字母转数字:A=10 ... Z=35values=[]iflen(letters)==1:values.append(36)# 单字母时,前面补空格=36values.append(ord(letters[0])-ord('A')+10)else:# 双字母values.append(ord(letters[0])-ord('A')+10)values.append(ord(letters[1])-ord('A')+10)# 2. 数字部分forchindigits:values.append(int(ch))# 3. 加权求和(权重从9开始递减)weights=[9,8,7,6,5,4,3,2]total=sum(v*wforv,winzip(values,weights))# 4. 取余remainder=total%11# 5. 校验码规则ifremainder==0:return'0'elifremainder==1:return'A'else:returnstr(11-remainder)defvalidate_hkid(hkid:str)->bool:"""完整验证香港身份证号码(格式 + 校验码)"""parsed=parse_hkid(hkid)ifnotparsed:returnFalseletters,digits,check_digit=parsed calculated=calculate_hkid_check_digit(letters,digits)returncalculated==check_digit# 测试print(calculate_hkid_check_digit('A','123456'))# '3'print(validate_hkid('A123456(3)'))# Trueprint(validate_hkid('A123456(9)'))# False(校验码不对)收藏提示②:
validate_hkid这个函数是"两层验证"的完整实现——第一层正则查格式,第二层 mod 11 查校验码。直接用validate_hkid()就能判断一个香港身份证号码是否逻辑有效。注意:逻辑有效 ≠ 真实存在(它只能证明号码结构正确,不能证明这个身份证真的发给了某个人)。
三、香港车牌格式:传统 vs 自定义
香港车牌有两种,格式完全不同:
传统车牌:1-2 个字母 + 1-4 位数字,如AB 1234、A 1。
自定义车牌(2006年起):最多 8 个字符,可以是字母+数字+空格的任意组合,如MYCAR、L0VE U。
# 传统车牌:1-2字母 + 1-4数字(可能有空格分隔)TRADITIONAL_PLATE=re.compile(r'^([A-Z]{1,2})\s?(\d{1,4})$')# 自定义车牌:最多8个字符(字母/数字/空格)CUSTOM_PLATE=re.compile(r'^[A-Z0-9 ]{1,8}$')defparse_plate(plate:str)->str:"""识别香港车牌类型"""plate=plate.strip().upper()ifTRADITIONAL_PLATE.match(plate):returnf'传统车牌:{plate}'elifCUSTOM_PLATE.match(plate):returnf'自定义车牌:{plate}'return'无效车牌'print(parse_plate('AB 1234'))# 传统车牌print(parse_plate('MYCAR'))# 自定义车牌print(parse_plate('ABC12345'))# 无效(9个字符超限)四、香港电话号码格式
香港电话号码统一 8 位数字,但不同号段有不同含义:
- 固话:通常 2 或 3 开头
- 手机:4/5/6/9 开头
- 国际格式:
+852前缀
# 8位数字(可带 +852 或 00852 国际前缀)PHONE_PATTERN=re.compile(r'^(?:\+852|00852)?\s?([2-9]\d{7})$')defparse_phone(phone:str)->str:"""解析香港电话号码"""m=PHONE_PATTERN.match(phone.strip())ifnotm:return'无效电话号码'number=m.group(1)prefix=number[0]ifprefixin'23':returnf'固话:{number}'elifprefixin'4569':returnf'手机:{number}'else:returnf'其他:{number}'print(parse_phone('+852 2345 6789'))# 固话print(parse_phone('98765432'))# 手机(9开头)print(parse_phone('12345678'))# 无效(1开头,且只有7位数字)五、完整验证器(可直接复用)
把三个验证器整合成一个工具类:
importreclassHKDataValidator:"""香港身份证/车牌/电话格式验证器"""# 身份证HKID_PATTERN=re.compile(r'^([A-Z]{1,2})(\d{6})[((]([0-9A])[))]$')# 车牌TRADITIONAL_PLATE=re.compile(r'^([A-Z]{1,2})\s?(\d{1,4})$')CUSTOM_PLATE=re.compile(r'^[A-Z0-9 ]{1,8}$')# 电话PHONE_PATTERN=re.compile(r'^(?:\+852|00852)?\s?([2-9]\d{7})$')@staticmethoddef_letter_to_num(ch:str)->int:returnord(ch)-ord('A')+10@classmethoddefvalidate_hkid(cls,hkid:str)->bool:m=cls.HKID_PATTERN.match(hkid.strip().upper())ifnotm:returnFalseletters,digits,check_digit=m.group(1),m.group(2),m.group(3)# 构建值序列iflen(letters)==1:values=[36,cls._letter_to_num(letters[0])]else:values=[cls._letter_to_num(letters[0]),cls._letter_to_num(letters[1])]values+=[int(ch)forchindigits]# 加权求和weights=[9,8,7,6,5,4,3,2]total=sum(v*wforv,winzip(values,weights))remainder=total%11# 校验码ifremainder==0:calculated='0'elifremainder==1:calculated='A'else:calculated=str(11-remainder)returncalculated==check_digit@classmethoddefvalidate_plate(cls,plate:str)->bool:plate=plate.strip().upper()returnbool(cls.TRADITIONAL_PLATE.match(plate)orcls.CUSTOM_PLATE.match(plate))@classmethoddefvalidate_phone(cls,phone:str)->bool:returnbool(cls.PHONE_PATTERN.match(phone.strip()))# 使用示例validator=HKDataValidator()print(validator.validate_hkid('A123456(3)'))# Trueprint(validator.validate_hkid('A123456(9)'))# Falseprint(validator.validate_plate('AB 1234'))# Trueprint(validator.validate_phone('+852 9876 5432'))# True六、三个易错点
坑1:全角括号
香港用户用中文输入法时,括号经常是全角()。如果你只写\(...\)(半角),会漏掉一大批真实数据。正则里同时匹配半角和全角,用[((]和[))]。
坑2:字母转数字的偏移量
字母 A 对应数字10(不是 1)。这是最容易写错的地方——如果你用ord(ch) - ord('A') + 1,A 会变成 1,整个校验码全错。正确是+ 10:
# ❌ 错误:A=1# ✅ 正确:A=10def_letter_to_num(ch):returnord(ch)-ord('A')+10坑3:逻辑有效 ≠ 真实存在
校验码算法只能证明"这个号码的结构是自洽的"(校验码和前面的字符对得上)。它不能证明这个身份证真的发给了某个人。真正的身份核验需要调政府接口或人工核对,正则和校验码只是第一道防线。
收藏提示③:三个坑里,全角括号和字母偏移是最容易在真实项目里踩的。字母 A=10 不是 A=1——这个偏移量错了,整个校验码算法就崩了。
七、总结
正则表达式能解决的,是"格式对不对"。校验码算法能解决的,是"号码逻辑真不真"。香港身份证号码恰好是两者的完美结合——格式(1-2字母+6数字+校验码)适合正则,校验码(mod 11 加权)适合算法验证。
三个东西记住:
- 正则验证格式,校验码验证逻辑——两层验证缺一不可,这是数据校验的通用思路
- 香港身份证 A=10 不是 A=1——字母转数字的偏移量,是最容易写错的细节
- 全角括号是真实数据里的坑——中文输入法下,半角正则匹配不到全角括号
这篇文章是0810那篇"数据清洗"的自然延续——0810讲的是怎么处理Big5编码,这篇讲的是怎么校验数据格式。两者合起来,才是完整的"香港数据清洗与验证"闭环。
本文为 Python 正则表达式与校验码算法的技术分享。香港身份证校验码算法为公开标准(mod 11 加权),文中示例号码均为演示用途的假号码,不涉及任何真实个人数据。本文不构成任何身份核验建议。