对账的经典变体:不是两份流水对账(那份见 30行Python自动对账),而是两份花名册——技术部一份工资表、市场部一份工资表,老板要的是「同一个人两边发的钱对不对得上」。按姓名对?遇到重名或改名就穿帮。本篇按身份证号对账,本次完整实跑过:2 个文件 9 行数据(含故意埋的脏数据和「同证号不同名」陷阱),4 个证号全对出、1 人金额差异被标记、脏行跳过打印原因,重跑幂等(两次输出 md5 相同)。
#!/usr/bin/env python3
"""跨文件按身份证号对账:多个部门的工资表,同一人在不同文件里金额是否一致?"""
import sys, os
from collections import defaultdict
from openpyxl import load_workbook, Workbook
def norm(s):
return str(s).strip() if s is not None else ""
def main(paths, out):
recs = defaultdict(list) # 证号 -> [(来源, 姓名, 金额)]
skipped = []
for p in paths:
wb = load_workbook(p, data_only=True)
for ws in wb.worksheets:
header = None; col = None
for row in ws.iter_rows(values_only=True):
cells = [norm(c) for c in row]
if not any(cells):
continue
if header is None:
header = [norm(h) for h in row] # 坑①:表头先 strip
col = {h: i for i, h in enumerate(header)}
if not {"姓名","身份证号","应发工资"} <= set(header):
header = None
continue
try:
amount = float(cells[col["应发工资"]]) # 坑②:脏行兜底
except (KeyError, ValueError):
skipped.append(f"{os.path.basename(p)}::{ws.title} {cells}")
continue
recs[cells[col["身份证号"]]].append(
(f"{os.path.basename(p)}::{ws.title}", cells[col["姓名"]], amount))
outwb = Workbook(); ws1 = outwb.active; ws1.title = "对账结果"
ws1.append(["身份证号","姓名(各处)","各处金额","是否一致","来源"])
n_diff = 0
for idno in sorted(recs):
rs = recs[idno]
names = "/".join(sorted({r[1] for r in rs})) # 坑③:口径按证号不按姓名
amts = sorted({r[2] for r in rs})
ok = "一致" if len(amts) == 1 else "❌差异"
if len(amts) > 1: n_diff += 1
ws1.append([idno, names, "/".join(str(a) for a in amts), ok,
"; ".join(f"{r[0]}:{r[2]}" for r in rs)])
for s in skipped:
print(f"[跳过行] {s}", file=sys.stderr)
outwb.save(out)
print(f"共对账 {len(recs)} 个证号,金额差异 {n_diff} 人,跳过脏行 {len(skipped)} 行")
if __name__ == "__main__":
main(sys.argv[1:-1], sys.argv[-1])
构造两个文件:技术部 3 人(张三 8000 / 李四 9500 / 王五 7000.5),市场部 5 行——张三同证号填 8500(真差异)、赵六正常、还有一个「钱七」用了张三的身份证号但姓名不同填 5000(陷阱行)、孙八金额填 "abc"(脏行):
共对账 4 个证号,金额差异 1 人,跳过脏行 1 行 [跳过行] 市场部-9月工资.xlsx::9月 ['孙八', '999', 'abc'] -- 对账结果.xlsx 110101199001011234, 张三/钱七, 5000.0/8000.0/8500.0, ❌差异, 技术部-9月工资.xlsx::9月:8000.0; 市场部-9月工资.xlsx::9月:8500.0; 市场部-9月工资.xlsx::9月:5000.0 110101199203024321, 李四, 9500.0, 一致, ... 120102198811115678, 王五, 7000.5, 一致, ... 130103199507078765, 赵六, 6800.0, 一致, ...
「张三/钱七」这行就是最值钱的输出:同一个证号底下挂着两个名字、三种金额,不管这是填错、冒用还是重名录入,人肉对账基本看不出来。重跑一次输出文件 md5 完全相同,幂等。
"abc"、"待定"、"—" 太常见,float() 直接 ValueError 且崩在循环深处。必须 try/except 单行跳过 + 把跳过的行列到 stderr——不然用户根本不知道少了谁。defaultdict 收集再想换键,张三和钱七的钱就永久分不开了。聚合键从第一行收集时就要定成身份证号;判定「是否一致」用金额去重后的个数(len(set(amts))),而不是逐对比较——后者写起来绕还容易漏。流水对账是「两边各一条记录,按单号对齐找单边/金额差」;花名册对账是「同一个人在多份表里各有一条,比的是金额集合是否一致」。前者输出「缺了哪单」,后者输出「哪个人哪几处金额不一样」。选错模板对出来的结果没法看。