2026-10-05 · 喵窝资源铺
高频场景:技术部、市场部、外包部各发一份工资表,领导要一张合计表——同一个人出现在多份表里(还有外包),要跨簿把 3 月/4 月/5 月的数按人相加。用 VLOOKUP 硬凑,人一多、月份一多就又慢又容易漏一行。这篇给一个 60 行的 openpyxl 脚本:python3 merge_salary.py 'src/*.xlsx' 合并.xlsx 一条命令出结果。文中代码实跑验证,真实输出贴在下面。
| 需求 | 脚本的处理 |
|---|---|
| 多簿多表 | 通配符喂进来,每个工作簿的每个工作表都扫一遍 |
| 同名行相加 | 「王喵」在 3 份表里各一行,数值列自动累计成一行 |
| 文本型数字 | '3,000'、'¥3,000' 这类文本自动清洗成数字再计入 |
| 表头校验 | 哪份表表头对不上,直接报错指出文件名,不静默拼出错的表 |
坑1:Excel 里的 "3000" 经常是文本,不是数字。造测试数据时故意在一份表里把工资存成字符串'3000',第一版脚本用isinstance(v,(int,float))判断,结果这 3000 被直接漏加——合计表少钱,这可是工资!真实世界这种数据太常见了(系统导出、手工粘贴都会带出来)。正确做法是先把字符串清洗:replace(',','').replace('¥','')再尝试转 float,转不动的(真正的备注文字)才跳过。修完之后'3000'被正确计入,合计 6500。
坑2:表头不一致必须报错,不能各拼各的。故意造了一份表头是「名字」而不是「姓名」的表,如果脚本静默跳过,那一整个部门的人就整行消失了。正确做法是发现表头对不上就raise SystemExit并指出是哪个文件——宁可明确失败,不要静默出错(跟之前 PDF 拆分那篇的教训一脉相承)。
坑3:人名别用顺序对齐,必须用姓名做 key。第一反应是「第2行加第2行」,但两个部门的人员名单不保证同序同人数——测试里市场部 3 人、技术部 2 人,按行号对齐直接把赵汪的工资加到李汪头上。必须以第一列(姓名)为字典 key 求和,输出时排序保证结果可复现。
# -*- coding: utf-8 -*-
"""多工作簿按姓名合并工资/流水:同名工作表、同名行数值列相加。
用法: python3 merge_salary.py 'src/*.xlsx' 合并.xlsx
只依赖 openpyxl。
"""
import glob, argparse
from openpyxl import load_workbook, Workbook
def to_num(v):
"""文本型数字清洗:'3,000'/'¥3,000' -> 3000,真文本返回 None。"""
if isinstance(v, str):
s = v.replace(',', '').replace(',','').replace('¥','').replace('¥','').strip()
try: return float(s)
except ValueError: return None
return v
def merge(pattern, out):
header = None; data = {}
for f in sorted(glob.glob(pattern)):
wb = load_workbook(f, data_only=True)
for name in wb.sheetnames:
rows = list(wb[name].values)
if not rows: continue
h = [str(c) if c is not None else '' for c in rows[0]]
if header is None: header = h
if h != header:
raise SystemExit(f"表头不一致: {f}[{name}] {h} vs {header} —— 先统一表头再跑")
for r in rows[1:]:
if r[0] is None: continue
key = str(r[0])
data.setdefault(key, [0]*(len(header)-1))
for i in range(1, len(header)):
v = to_num(r[i] if i < len(r) else None)
if isinstance(v, (int, float)): data[key][i-1] += v
if header is None: raise SystemExit("没匹配到任何文件")
wb = Workbook(); ws = wb.active; ws.title = "合并"
ws.append(header)
for k in sorted(data): ws.append([k] + data[k])
wb.save(out); print("saved", out, len(data), "人")
if __name__ == "__main__":
p = argparse.ArgumentParser(); p.add_argument("pattern"); p.add_argument("out")
a = p.parse_args(); merge(a.pattern, a.out)
造了 3 份测试表:技术部(王喵 3000/3200/3100、李汪)、市场部(王喵 500/600/700、赵汪)、外包部(王喵工资故意存成文本 '3000'),再加一份表头写成「名字」的坏表:
$ python3 merge_salary.py 'src/*.xlsx' 合并.xlsx
saved 合并.xlsx 3 人
$ python3 -c "from openpyxl import load_workbook; [print(r) for r in load_workbook('合并.xlsx').active.values]"
('姓名', '3月', '4月', '5月')
('李汪', 2800, 2800, 3000)
('王喵', 6500, 7000, 6900)
('赵汪', 1500, 1500, 1600)
核对「王喵」:3 月 = 3000 + 500 + 3000(文本清洗后) = 6500 ✓;4 月 = 3200 + 600 + 3200 = 7000 ✓;5 月 = 3100 + 700 + 3100 = 6900 ✓。跨簿同名行求和、文本型数字清洗全部正确。
喂入表头不一致的坏表:
表头不一致: bad/坏表.xlsx[Sheet] ['名字', '3月', ...] vs ['姓名', '3月', ...] —— 先统一表头再跑
明确指出是哪个文件的哪列对不上,而不是悄悄丢掉一整个部门。