多个 Sheet 汇总成一个总表(含 4 个实跑大坑)

台账类 Excel 的经典形态:一个工作簿里「1月」「2月」「3月」各一个 Sheet,列都一样;更狠的是几十个文件、每个里面还有同名 Sheet,老板要一张全年总表 + 按人汇总。手动复制粘贴 Sheet 又是体力刑具。下面这份脚本本次完整实跑过:3 个月度 Sheet 共 7 行数据(含故意埋的脏数据),合并 + 按人汇总全部正确,非法行跳过并打印原因,重跑幂等。

核心脚本

#!/usr/bin/env python3
"""多 Sheet 汇总:把一个工作簿里所有月份 sheet 的销售记录合并成总表,并按人汇总。"""
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):
    all_rows = []
    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)}
                    missing = {"姓名","日期","金额"} - set(header)
                    if missing:
                        print(f"[跳过sheet] {p}::{ws.title} 缺列{missing}", file=sys.stderr)
                        header = None; col = None
                    continue
                d = cells[col["日期"]]
                if hasattr(row[col["日期"]], "strftime"):    # 坑④:真日期单元格读出 datetime 对象
                    d = row[col["日期"]].strftime("%Y-%m-%d")
                try:
                    amount = float(cells[col["金额"]])   # 坑③:非数字金额会 ValueError
                except (KeyError, ValueError):
                    print(f"[跳过行] {p}::{ws.title} {cells}", file=sys.stderr)
                    continue
                all_rows.append({
                    "来源sheet": f"{os.path.basename(p)}::{ws.title}",
                    "姓名": cells[col["姓名"]],
                    "日期": d,
                    "金额": amount,
                })
    total, cnt = defaultdict(float), defaultdict(int)
    for r in all_rows:
        total[r["姓名"]] += r["金额"]; cnt[r["姓名"]] += 1
    outwb = Workbook()
    ws1 = outwb.active; ws1.title = "明细汇总"
    ws1.append(["来源sheet","姓名","日期","金额"])
    for r in all_rows: ws1.append([r["来源sheet"],r["姓名"],r["日期"],r["金额"]])
    ws2 = outwb.create_sheet("按人汇总")
    ws2.append(["姓名","单数","总金额"])
    for name in sorted(total): ws2.append([name,cnt[name],round(total[name],2)])
    outwb.save(out)
    print(f"共合并 {len(all_rows)} 行,来自 {len(set(r['来源sheet'] for r in all_rows))} 个sheet")

if __name__ == "__main__":
    main(sys.argv[1:-1], sys.argv[-1])

实跑结果

构造 3 个 Sheet(1月/2月 正常,3月 故意埋雷:表头写成 " 姓名 " 带空格、一行整行空白、一行金额填 "abc"):

共合并 6 行,来自 3 个sheet
[跳过行] 销售台账.xlsx::3月 ['赵六', '2026-03-15', 'abc']

-- 明细汇总
销售台账.xlsx::1月, 张三, 2026-01-03, 15000.5
销售台账.xlsx::1月, 李四, 2026-01-05, 8200
销售台账.xlsx::1月, 张三, 2026-01-20, 3000
销售台账.xlsx::2月, 李四, 2026-02-01, 9900
销售台账.xlsx::2月, 王五, 2026-02-11, 4560.75
销售台账.xlsx::3月, 张三, 2026-03-02, 12000
-- 按人汇总
张三, 3单, 30000.5
李四, 2单, 18100
王五, 1单, 4560.75

两个文件(A公司/B公司,各有同名「1月」Sheet)也实跑通过,来源列能区分是哪个文件的哪张表。重跑输出完全一致,幂等。

4 个实跑大坑

坑 1:表头不 strip 就对不上列。真实台账的表头经常是 " 姓名 "——前后空格看不见但真实存在。用裸字符串做字典键 {"姓名":0},col["姓名"] 直接 KeyError,而且报错在数据处理循环里,不在表头那行,第一眼根本想不到是表头脏。所有表头读进来先 strip() 归一化。
坑 2:真日期单元格读出来是 datetime 对象。单元格里如果是真的日期格式(不是文本),iter_rows(values_only=True) 读出的是 datetime.datetime(2026,1,3,0,0)。str() 一转就变成 "2026-01-03 00:00:00",混进全是 "2026-01-05" 文本日期的列里两种格式并存,排序、筛选全乱。修法:hasattr(c,'strftime') 判断后统一 strftime("%Y-%m-%d")。
坑 3:非法金额不兜底,一个 "abc" 毁掉整批。财务台账里金额列出现「待定」「—」或者手滑的 "abc" 太常见了。float(c) 遇到直接 ValueError,整个脚本崩在最后一行数据上,前面白跑。必须 try/except 单行跳过 + 打印原因到 stderr,让用户知道哪几行被跳过、为什么。
坑 4:多文件同名 Sheet 分不清来源。合并几十个文件时,每个文件里都有「1月」,只记 Sheet 名的话 50 行「1月」根本没法溯源是哪家公司的。来源列要带文件名:f"{os.path.basename(p)}::{ws.title}"。顺带一提:空行不跳过的话,表头判断会错位——「下一条非空行当表头」的逻辑会把数据行误认成第二张表的表头。

为什么不用 pandas?

pandas.concat(pd.read_excel(path, sheet_name=None)) 确实一行搞定合并,但:目标机器装 pandas + openpyxl 是一大坨依赖;datetime/表头空格/非法金额这几个坑 pandas 一样要手写清洗逻辑;而且「按人汇总」这种带业务口径的统计,写出来还是十几行。对「在别人电脑上跑」的场景(详见 脚本在别人电脑上报错怎么办),openpyxl 一个包就够了,坑也全在自己看得见的地方。

没时间自己改?这是 喵窝资源铺 的常用脚本场景之一:9.9 元起,把你的 Excel 发来,AI 写脚本+人工核验结果后交付,虚拟物品不退款但免费重做到结果正确。更多实跑验证过的脚本见 文章合集。