2026-10-06 · 喵窝资源铺
收表的人都知道:表格里的身份证号总有几个是随手乱填的。位数不够的、出生日期是13月32日的、末位数字对不上的——人工抽查几百行根本看不过来。这篇讲清楚身份证号的内部结构,给一个 30 行的 Python 脚本批量核验,能揪出三类错误。文中代码实跑验证,真实输出贴在下面。
| 位置 | 内容 |
|---|---|
| 第1–6位 | 地址码(省市区),只影响归属地,不参与有效性判断 |
| 第7–14位 | 出生日期 YYYYMMDD,必须是真实存在的日期 |
| 第15–17位 | 顺序码(同地区同日生人编号,奇男偶女) |
| 第18位 | 校验码:由前17位加权求和取模算出,可为数字或 X |
前17位各乘一个加权因子后求和,对 11 取模,从字符表里查对应字符:
W = [7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2] M = "10X98765432" 校验码 = M[sum(位 × 权) % 11]
这套机制意味着:随便编一个号,有约 10/11 的概率第18位对不上——校验码就是专门防乱填的。反向也成立:知道前17位可以反推出合法的第18位(测试脚本时我们就这么造测试数据,确保算法自洽)。
# -*- coding: utf-8 -*-
"""批量核验18位身份证号是否合法(格式/出生日期/校验码)。
用法: python3 idv.py 号1 号2 号3 ... 或配合 csv 逐行调用
"""
import re, sys, datetime
W = [7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2]
M = "10X98765432"
def check(idn):
idn = idn.strip().upper() # 末位小写 x 统一转大写
if not re.fullmatch(r'\d{17}[\dX]', idn):
return "格式错误:应为18位,末位可为X"
y, m, d = int(idn[6:10]), int(idn[10:12]), int(idn[12:14])
try:
datetime.date(y, m, d)
except ValueError:
return f"出生日期非法:{y}-{m:02d}-{d:02d}"
s = sum(int(c)*w for c, w in zip(idn[:17], W))
if M[s % 11] != idn[17]:
return f"校验位错误:应为 {M[s % 11]}"
return "OK"
for t in sys.argv[1:]:
print(t, "->", check(t))
$ python3 idv.py 110101199003077774 11010519491231002X 11010119900307777X 110101199003077 110101199013077772 110101199003077774 -> OK 11010519491231002X -> OK 11010119900307777X -> 校验位错误:应为 4 110101199003077 -> 格式错误:应为18位,末位可为X 110101199013077772 -> 出生日期非法:1990-13-07
五条测试数据覆盖五类情况:合法号 OK、末位为X的合法号 OK、错校验位、短号、13月非法日期,全部判定正确。另用「前17位反推校验位」验证了算法自洽:对测试号算出应为 4,与脚本判定一致。
x,直接比对 [\dX] 会误判格式错,先 upper()。strip() 只清半角,保险起见 idn.replace('\u3000','').strip()。datetime.date() 抛异常判断,不需要单独写月天数表。这个脚本校验的是号码本身是否合法(自洽),不等于「这个号真的属于某个人」——合法结构的号也可以是编出来的。要核验人证一致性必须走官方实名认证接口。它适合的场景是:收表时先过滤掉明显乱填的行,减少人工复核量。