夜雨聆风学习资料网

ARTICLE · 1027261

Python 批量合并多个 Excel 文件(附完整代码,可直接套用)

Python 批量合并多个 Excel 文件(附完整代码,可直接套用)

同事手动复制粘贴 3 小时,我用 Python 30 秒搞定。
本文代码可直接复制运行,改两个路径就能用。


一、先说说痛点

每个月做报表,是不是都要经历这一幕:

📁 销售一部.xlsx
📁 销售二部.xlsx
📁 销售三部.xlsx
…… (一共 37 个)

打开 → 全选 → 复制 → 粘贴到汇总表 → 关闭 → 下一个……

37 个文件,手指点麻了,还容易漏、容易串行。

这种重复劳动,就该交给代码。


二、整体思路

不管文件有多少,逻辑永远是三步:

遍历文件夹 → 逐个读取成 DataFrame → 纵向拼接 → 写出一个文件

关键就三个函数:

函数作用
os.listdir() / Path.rglob()找出所有 Excel 文件
pd.read_excel()读成表格
pd.concat()上下拼起来
to_excel()写出结果

三、环境准备

pip install pandas openpyxl
  • pandas:数据处理核心

  • 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["来源工作表"] = sname    dfs.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")
行数超过 10 万行 时,建议直接输出 CSV,比 xlsx 快一个数量级:
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 None                      else {SHEET_NAME: pd.read_excel(p, sheet_name=SHEET_NAME)})            for sname, df in sheets.items():                if df.empty:                    continue                if COLS:                    df = df.reindex(columns=COLS)                df["来源文件"] = p.name                if SHEET_NAME is None:                    df["来源工作表"] = sname                dfs.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?

  • 列名统一吗?

  • 出错了怎么办?

把这些都想清楚,你的脚本就从"能跑"变成了"能交给同事用"。

相关学习资料