《超简单:用 Python 让 Excel 飞起来》读书笔记:第4章 案例09 批量合并多个工作簿中的同名工作表
《超简单:用 Python 让 Excel 飞起来》读书笔记:第4章 案例09 批量合并多个工作簿中的同名工作表
- 1. 问题背景:为什么要批量合并同名工作表
- 2. 应用场景:多个分部统计表合成一份汇总表
- 3. 核心原理:表头只取一次,数据持续累积
- 4. 实现流程:遍历文件、读取同名表、写入汇总簿
- 5. 关键代码拆解:为什么这样写
- 5.1 为什么要跳过临时文件
- 5.2 为什么要判断同名工作表是否存在
- 5.3 为什么用 expand("table")
- 5.4 为什么最后要 autofit()
- 6. 效果验证:汇总后必须检查这几项
- 7. 常见问题与踩坑提醒
- 7.1 表头不一致会导致汇总结果变脏
- 7.2 空行和合并单元格会影响读取范围
- 7.3 文件被打开可能导致保存异常
- 7.4 xlwings 依赖 Excel 环境
- 8. 举一反三:一个工作簿内合并 1月 到 6月
- 9. 总结提升:合并不是复制,合并是规则化处理
1. 问题背景:为什么要批量合并同名工作表
这一篇继续整理《超简单:用 Python 让 Excel 飞起来》第 4 章中的案例内容,主题是批量合并多个工作簿中的同名工作表。这个场景在真实办公里很常见:多个部门、多个分部、多个项目分别提交 Excel 文件,每个文件里都有一张同名工作表,最后需要汇总成一份总表。
比如华北、华东、华南三个分部各自提交一个上半年.xlsx文件,每个文件里都有一张叫统计的工作表。现在领导不想看三个文件,只想看一份上半年汇总表.xlsx。如果手工处理,就要不断打开文件、复制数据、粘贴到总表,这就是非常典型的“复制粘贴型加班”。
这张图展示的是本文的整体目标:把多个工作簿中的同名工作表,批量合并到一个新的汇总工作簿中。
从图中可以看出,本文的重点不是处理单个 Excel,而是把多个来源文件的数据统一汇入一个结果文件。这类任务的核心不是“会不会复制”,而是能不能稳定地批量读取、累积数据、生成汇总结果。
这里最容易翻车的地方,是把每个工作簿的表头都一起复制进去。如果每合并一个文件就追加一次表头,最后的汇总表里会反复出现“日期、产品、销量、金额”这样的标题行,后续筛选、排序、透视分析都会受到影响。
2. 应用场景:多个分部统计表合成一份汇总表
批量合并同名工作表,本质上解决的是“分散数据集中化”的问题。只要多个 Excel 文件结构相似,且目标工作表名称一致,就可以考虑用脚本自动合并。
这张图展示的是典型应用场景:多个分部统计表,最终汇总成一份总表。
从图中可以看到,这类需求常见于财务汇总、销售汇总、库存汇总等场景。每个分部都有自己的统计表,但管理层真正需要的是一份统一结果。Python 在这里的价值,就是把重复复制粘贴变成稳定规则。
我更建议把这类任务理解成一个标准流程:先确定文件夹,再逐个打开工作簿,找到指定同名工作表,读取数据,持续累积,最后统一写入一个新的汇总工作簿。
这张流程图里最重要的是两个判断:第一,目标工作表是否存在;第二,当前是不是第一次读取数据。前者决定文件要不要跳过,后者决定是否要读取表头。
3. 核心原理:表头只取一次,数据持续累积
批量合并多个工作簿时,最关键的逻辑不是打开文件,而是处理表头和数据的关系。正常情况下,每个工作簿里的同名工作表都有一行表头,但最终汇总表只需要一份表头。
这张图展示的是本案例的核心逻辑:第一次读取时保留表头,后续工作簿只追加数据行。
从图中可以看出,header = None表示表头还没有被初始化,all_data = []表示先准备一个空列表,用来持续累积后续读取到的数据行。第一次遇到有效表格时,取出表头;之后每读取一个文件,就只把数据部分追加到all_data中。
header = None的语义是“还没有拿到表头”,而不是“表头为空”。这个区别很重要。None更像一个初始化标记,表示后面第一次读取到表格时,需要把第一行保存为表头。
header=Noneall_data=[]ifheaderisNone:header=table[0]values=table[1:]all_data=all_data+values这里的table[0]表示第一行,也就是表头;table[1:]表示从第二行开始的所有数据行。最终写入汇总表时,用[header] + all_data拼成完整内容。
如果不区分表头和数据,汇总表后期会很难用。重复表头会影响筛选、排序、透视表、公式引用,也会让数据看起来不干净。这种问题不是语法错误,但属于典型的数据质量问题。
4. 实现流程:遍历文件、读取同名表、写入汇总簿
在看完整代码之前,先把流程想清楚。这个案例可以拆成八个动作:遍历文件夹、跳过临时文件、检查是否存在目标工作表、读取表格数据、累积到all_data、新建汇总工作簿、写入表头和数据、最后执行autofit()自动调整显示效果。
这张图展示的是完整实现流程,从文件遍历一直到最终生成汇总表。
从图中可以看出,真正稳定的脚本不是“打开文件然后复制”,而是每一步都有判断。比如遇到~$临时文件要跳过;目标工作表不存在要跳过;数据为空也要跳过。批处理脚本越是面对多个文件,越不能假设每个文件都完全正常。
下面是一份可直接修改使用的完整代码。实际使用时,只需要改三个参数:输入文件夹、目标工作表名称、输出文件路径。
importosimportxlwingsasxw# ====== 需要根据实际情况修改的参数 ======folder_path=r"e:\file\target"# 多个工作簿所在目录sheet_name="统计"# 需要合并的同名工作表out_file=r"e:\file\上半年汇总表.xlsx"# 输出汇总文件# ======================================app=xw.App(visible=False,add_book=False)header=Noneall_data=[]try:forfileinos.listdir(folder_path):# 1. 跳过 Excel 临时文件iffile.startswith("~$"):continue# 2. 只处理 Excel 文件ifnotfile.lower().endswith((".xlsx",".xls",".xlsm")):continuefull_path=os.path.join(folder_path,file)wb=app.books.open(full_path)# 3. 判断目标工作表是否存在sheet_names=[s.nameforsinwb.sheets]ifsheet_namenotinsheet_names:wb.close()print(f"跳过:{file},不存在工作表:{sheet_name}")continuesht=wb.sheets[sheet_name]# 4. 从 A1 开始扩展读取表格区域table=sht.range("A1").expand("table").value# 5. 判断是否有有效数据ifnottableorlen(table)<2:wb.close()print(f"跳过:{file},数据为空或只有表头")continue# 6. 第一次读取表头ifheaderisNone:header=table[0]# 7. 后续只累积数据行values=table[1:]all_data=all_data+values wb.close()print(f"已读取:{file}-> [{sheet_name}]")# 8. 新建汇总工作簿out_wb=app.books.add()out_sht=out_wb.sheets[0]out_sht.name="汇总"ifheaderisNone:out_sht.range("A1").value=[["没有读取到任何有效数据"]]else:out_sht.range("A1").value=[header]+all_data out_sht.autofit()out_wb.save(out_file)out_wb.close()print(f"汇总完成:{out_file}")finally:app.quit()这段代码的骨架很清楚:外层负责遍历文件,内层负责读取指定工作表,最后统一写入汇总工作簿。它不是一次性脚本,而是一个可以复用的批量合并模板。
5. 关键代码拆解:为什么这样写
5.1 为什么要跳过临时文件
Excel 打开文件时,目录里可能出现~$开头的临时文件。这类文件不是正式工作簿,脚本如果尝试打开它,很容易报错。
iffile.startswith("~$"):continue推荐所有批量处理 Excel 的脚本都加上这个判断。这不是多余代码,而是稳定性保障。
5.2 为什么要判断同名工作表是否存在
并不是所有文件都一定有目标工作表。比如有些分部没有提交统计表,或者表名写成了数据统计。如果不判断直接读取,就会报错。
sheet_names=[s.nameforsinwb.sheets]ifsheet_namenotinsheet_names:wb.close()print(f"跳过:{file},不存在工作表:{sheet_name}")continue不要默认所有文件都符合模板。真实办公里,文件命名、sheet 命名、字段顺序经常会有偏差。脚本必须允许异常文件跳过,而不是因为一个文件异常导致整个任务中断。
5.3 为什么用 expand(“table”)
sht.range("A1").expand("table").value的意思是从A1单元格开始,自动扩展读取连续的数据区域。只要表格从A1开始,并且中间没有完全空白行列,这种写法就比较方便。
table=sht.range("A1").expand("table").value这里有一个前提:表格必须从 A1 附近开始,并且数据区域要连续。如果表格上方有标题说明、空行、合并单元格,expand("table")可能无法读到你想要的区域。这类文件需要先规范模板,或者改用更明确的读取范围。
5.4 为什么最后要 autofit()
写入 Excel 后,列宽可能不合适,长文本会被遮住,数字也可能显示成#######。所以写完数据后执行autofit(),能让汇总表更接近可阅读状态。
out_sht.autofit()推荐在生成结果文件时做基础美化。办公自动化不是只把数据写进去,还要考虑别人打开文件时能不能直接看。
6. 效果验证:汇总后必须检查这几项
批量合并脚本跑完后,不能只看控制台有没有报错。更稳的做法,是对输出结果做基本校验。
第一,要检查输出文件是否生成;第二,要检查汇总表是否只有一行表头;第三,要检查数据行数是否符合预期;第四,要抽查几条来源数据,看是否确实进入汇总表。
print(f"汇总数据行数:{len(all_data)}")print(f"输出文件:{out_file}")如果希望日志更清楚,可以记录每个文件的处理状态。比如哪些文件成功读取,哪些文件缺少目标 sheet,哪些文件为空。这样后续复盘更方便。
log_list=[]log_list.append({"文件名":file,"目标工作表":sheet_name,"处理结果":"已读取","数据行数":len(values)})对于真实办公场景,我更建议把处理日志也导出成 Excel。因为脚本输出的结果文件可能会被别人复查,处理日志就是你的证据链。
7. 常见问题与踩坑提醒
7.1 表头不一致会导致汇总结果变脏
如果不同工作簿里的统计表字段不一致,比如有的叫销售额,有的叫金额,脚本虽然可能能合并,但最终结果会出现字段含义不统一的问题。
合并前最好先校验表头是否一致。否则看起来合并成功,实际数据口径可能已经错了。
ifheaderisnotNoneandtable[0]!=header:print(f"表头不一致:{file}")7.2 空行和合并单元格会影响读取范围
expand("table")适合连续表格。如果 Excel 里有空行、合并单元格、额外标题、说明文字,读取范围可能不符合预期。
推荐先统一模板,再批量合并。如果模板混乱,脚本只是把混乱放大,不能自动把所有脏数据变干净。
7.3 文件被打开可能导致保存异常
如果某个源文件正在被打开,或者输出汇总文件已经被打开,脚本可能无法保存。尤其是在共享盘、同步盘、企业网盘目录中,这个问题很常见。
建议先把待处理文件复制到本地临时目录,例如C:\Temp\excel_merge,确认无人占用后再执行脚本。
7.4 xlwings 依赖 Excel 环境
xlwings的优势是能像人工一样操作 Excel,但它通常依赖本机安装的 Excel。如果电脑没有安装 Excel,或者 Excel 插件、弹窗、权限策略异常,脚本也可能运行失败。
如果只是读取和合并数据,后续也可以考虑用 pandas + openpyxl 做无界面处理。但本文沿用书中思路,重点练习的是 xlwings 操控工作簿和工作表的能力。
8. 举一反三:一个工作簿内合并 1月 到 6月
前面的案例是多个工作簿合并到一个汇总簿。其实同样的思路也可以用于一个工作簿内部,比如一个年度数据.xlsx中有1月、2月、3月、4月、5月、6月这些工作表,现在要合并成一张上半年汇总。
这张图展示的是扩展案例:在同一个工作簿中,把 1月 到 6月 的工作表合并到一张新表中。
从图中可以看出,虽然合并范围从“多个文件”变成了“一个文件里的多个 sheet”,但底层逻辑没有变:第一次取表头,后续只取数据,最后写入新的汇总工作表。
importxlwingsasxw file_path=r"e:\file\年度数据.xlsx"month_sheets=["1月","2月","3月","4月","5月","6月"]app=xw.App(visible=False,add_book=False)header=Noneall_data=[]try:wb=app.books.open(file_path)fornameinmonth_sheets:sheet_names=[s.nameforsinwb.sheets]ifnamenotinsheet_names:print(f"跳过:不存在工作表{name}")continuesht=wb.sheets[name]table=sht.range("A1").expand("table").valueifnottableorlen(table)<2:print(f"跳过:{name}数据为空或只有表头")continueifheaderisNone:header=table[0]all_data=all_data+table[1:]if"上半年汇总"notin[s.nameforsinwb.sheets]:wb.sheets.add("上半年汇总")sum_sht=wb.sheets["上半年汇总"]sum_sht.clear()ifheaderisNone:sum_sht.range("A1").value=[["没有读取到任何有效数据"]]else:sum_sht.range("A1").value=[header]+all_data sum_sht.autofit()wb.save()wb.close()finally:app.quit()这个扩展案例非常适合练习“同一逻辑迁移到不同场景”。如果能把多个文件合并、同文件多 sheet 合并都理解清楚,后续处理月报、季度报、分部报表会轻松很多。
9. 总结提升:合并不是复制,合并是规则化处理
这一篇的核心,不是简单记住all_data = all_data + values,而是理解批量合并背后的自动化思维。多个工作簿中的同名工作表,本质上是多个结构相似的数据源;汇总工作簿,则是统一输出结果。
本文最关键的判断有三点:第一,目标工作表是否存在;第二,是否第一次读取表头;第三,读取到的数据是否有效。只要这三点处理好,脚本的稳定性就会明显提高。
真正可复用的脚本,不是只会在一次练习文件上跑通,而是能处理异常、能跳过无效文件、能输出清晰结果。这才是办公自动化和简单代码练习之间的区别。
最后提醒一句:批量合并前一定要备份源文件,并用少量样本先测试。尤其是涉及财务、库存、资产、人事等数据时,脚本跑得快不是优点,跑得稳、结果可验证才是优点。
🔝 返回顶部
点击回到顶部
