ARTICLE · 1027261
Python 批量合并多个 Excel 文件(附完整代码,可直接套用)
Python 批量合并多个 Excel 文件(附完整代码,可直接套用)
行数超过 10 万行 时,建议直接输出 CSV,比 xlsx 快一个数量级:
同事手动复制粘贴 3 小时,我用 Python 30 秒搞定。
本文代码可直接复制运行,改两个路径就能用。
一、先说说痛点
每个月做报表,是不是都要经历这一幕:
📁 销售一部.xlsx
📁 销售二部.xlsx
📁 销售三部.xlsx
…… (一共 37 个)
打开 → 全选 → 复制 → 粘贴到汇总表 → 关闭 → 下一个……
37 个文件,手指点麻了,还容易漏、容易串行。
这种重复劳动,就该交给代码。
二、整体思路
不管文件有多少,逻辑永远是三步:
遍历文件夹 → 逐个读取成 DataFrame → 纵向拼接 → 写出一个文件关键就三个函数:
| 函数 | 作用 |
|---|---|
os.listdir() / Path.rglob() | 找出所有 Excel 文件 |
pd.read_excel() | 读成表格 |
pd.concat() | 上下拼起来 |
to_excel() | 写出结果 |
三、环境准备
pip install pandas openpyxlpandas:数据处理核心openpyxl:读写.xlsx(老版.xls再装xlrd)
四、基础版(20 行搞定)
先看最小可用版本,看懂这段,后面都是锦上添花。
# -*- coding: utf-8 -*-import osimport pandas as pdFOLDER = r"D:\待合并表格" # 表格所在文件夹OUTPUT = r"D:\待合并表格\合并结果.xlsx" # 输出文件# 1. 找出所有 Excel 文件(过滤掉 Excel 打开时生成的 ~$ 临时文件)files = [f for f in os.listdir(FOLDER)if f.endswith((".xlsx", ".xls")) and not f.startswith("~$")]print(f"共找到 {len(files)} 个文件")# 2. 逐个读取,并打上来源标记dfs = []for name in files:df = pd.read_excel(os.path.join(FOLDER, name))df["来源文件"] = name # 方便回溯数据来自哪张表,不需要可删dfs.append(df)# 3. 纵向拼接 + 写出result = pd.concat(dfs, ignore_index=True)result.to_excel(OUTPUT, index=False)print(f"合并完成:{len(result)} 行 → {OUTPUT}")
跑一下,几十个文件几秒就完事。
五、进阶版:把这些坑都填上
实际工作中,你一定会遇到下面这些问题👇
坑 1:表头不在第一行
有些表前面有标题、说明行,第一行不是表头。
# 表头在第 3 行(索引从 0 开始)df = pd.read_excel(path, header=2)# 或者:跳过前 2 行,再用第 1 行当表头df = pd.read_excel(path, skiprows=2)
坑 2:只想合并某个 Sheet
# 只读名为 "明细" 的工作表df = pd.read_excel(path, sheet_name="明细")# 只读第一个工作表(默认)df = pd.read_excel(path, sheet_name=0)
坑 3:每个文件有多个 Sheet,全都要
sheets = pd.read_excel(path, sheet_name=None) # 返回字典 {表名: DataFrame}for sname, df in sheets.items():df["来源工作表"] = snamedfs.append(df)
坑 4:列的顺序/名称不一致
有的文件列多,有的列少,concat 后会出现一堆 NaN。
# 只保留需要的列,缺失的自动补 NaN,多余的丢弃COLS = ["姓名", "工号", "部门", "金额"]df = df.reindex(columns=COLS)
坑 5:文件太多 / 太大,读得慢
# 用 calamine 引擎,读取速度能快 3~5 倍# 先安装:pip install python-calaminedf = pd.read_excel(path, engine="calamine")
result.to_csv("合并结果.csv", index=False, encoding="utf-8-sig")
utf-8-sig是为了让 Excel 打开 CSV 不乱码,别省。
坑 6:某个文件损坏,程序直接崩了
一定要用 try / except 包起来,坏一个不能影响全局。
六、完整可用脚本(推荐直接复制)
支持:子文件夹递归、多 Sheet、指定列、关键词过滤、异常跳过、失败清单。
# -*- coding: utf-8 -*-"""批量合并 Excel 工具用法:修改下面【配置区】的路径,直接运行即可"""import sysimport tracebackfrom pathlib import Pathimport pandas as pd# ==================== 配置区 ====================FOLDER = r"D:\待合并表格" # 表格所在文件夹(含子文件夹)OUTPUT = r"D:\待合并表格\合并结果.xlsx" # 输出文件,后缀 .xlsx 或 .csvSHEET_NAME = 0 # 0=第一个表;None=所有表;也可写 "明细"COLS = None # 如 ["姓名","部门","金额"];None=保留全部列KEYWORD = None # 如 "2024";只合并文件名含该关键词的文件# ===============================================def main():folder = Path(FOLDER)if not folder.exists():sys.exit(f"❌ 文件夹不存在:{folder}")out_path = Path(OUTPUT)# 1. 收集文件(递归子目录,排除临时文件和输出文件本身)files = sorted(p for p in folder.rglob("*")if p.suffix.lower() in (".xlsx", ".xls")and not p.name.startswith("~$")and p.resolve() != out_path.resolve()and (KEYWORD is None or KEYWORD in p.name))if not files:sys.exit("❌ 没有找到符合条件的 Excel 文件")print(f"📂 共找到 {len(files)} 个文件,开始合并...\n")# 2. 逐个读取dfs, failed = [], []for i, p in enumerate(files, 1):try:sheets = (pd.read_excel(p, sheet_name=None)if SHEET_NAME is Noneelse {SHEET_NAME: pd.read_excel(p, sheet_name=SHEET_NAME)})for sname, df in sheets.items():if df.empty:continueif COLS:df = df.reindex(columns=COLS)df["来源文件"] = p.nameif SHEET_NAME is None:df["来源工作表"] = snamedfs.append(df)print(f"[{i}/{len(files)}] ✅ {p.name}")except Exception as e:failed.append((p.name, str(e)))print(f"[{i}/{len(files)}] ❌ {p.name} —— {e}")if not dfs:sys.exit("❌ 所有文件都读取失败,请检查格式")# 3. 合并并输出result = pd.concat(dfs, ignore_index=True)if out_path.suffix.lower() == ".csv":result.to_csv(out_path, index=False, encoding="utf-8-sig")else:result.to_excel(out_path, index=False)# 4. 汇总报告print("\n" + "=" * 45)print(f"🎉 合并完成:{len(result)} 行 × {len(result.columns)} 列")print(f"📄 输出文件:{out_path}")if failed:print(f"\n⚠️ 以下 {len(failed)} 个文件失败:")for n, e in failed:print(f" - {n}:{e}")print("=" * 45)if __name__ == "__main__":main()
运行前记得先备份原始文件,代码再稳也怕意外。
七、加分项:给不会写代码的同事用
方案 A:做个选文件夹的小窗口
import tkinter as tkfrom tkinter import filedialogroot = tk.Tk()root.withdraw()FOLDER = filedialog.askdirectory(title="请选择存放 Excel 的文件夹")print("已选择:", FOLDER)
把这行加在脚本开头,同事双击就能用。
方案 B:打包成 exe,双击即用
pip install pyinstallerpyinstaller -F -w 合并Excel.py
dist/ 目录下就会生成一个 合并Excel.exe,发给同事直接跑,不需要装 Python。
八、几个容易踩的坑
| 现象 | 原因 | 解决 |
|---|---|---|
PermissionError | 文件正被 Excel 打开 | 先全部关闭 |
ImportError: xlrd | 读 .xls 缺库 | pip install xlrd==1.2.0 |
| 日期变成 45000 这种数字 | 单元格格式问题 | pd.to_datetime(df["日期"], unit="D", origin="1899-12-30") |
| 身份证号末尾变 000 | 被当数字读了 | dtype={"身份证": str} |
表头变成 Unnamed: 0 | 首列是空列/索引列 | pd.read_excel(path, index_col=0) |
九、小结
核心就一句话:
把每个 Excel 读成 DataFrame,用
pd.concat摞起来,再写出。
在此基础上,你需要考虑的无非是:
表头在第几行?
要哪个 Sheet?
列名统一吗?
出错了怎么办?
把这些都想清楚,你的脚本就从"能跑"变成了"能交给同事用"。