🧾新手也能懂的 Excel 批次合併:用 Python 一鍵把多個檔案變成一個活頁簿(每檔一張工作表)

1) 這篇文章要解決什麼問題?

你的同事寄了十幾個甚至幾百個 Excel 報表,你想「把它們合成一個檔案」、每個來源檔變成同一本活頁簿中的一張工作表。手動複製貼上很花時間,而且容易出錯。
我們要做的是:用一支小工具,自動化完成這件事。


2) 需要準備什麼(環境與觀念)

  • Python 3.9+(或相近版本)

  • 套件pandas(資料處理)、openpyxl(寫出 .xlsx)

    • 如果要讀更舊的 .xls,可另外裝 xlrd

    • 如果要讀 .xlsb,可另外裝 pyxlsb

  • 基本觀念

    • 資料夾:把要合併的 Excel 檔放在同一個資料夾。

    • 工作表名稱限制(Excel):不可含 []:*?/ \,最多 31 個字,且同一本活頁簿中不可重複。

安裝套件(命令列):

pip install pandas openpyxl # (選用) pip install xlrd pyxlsb

3) 流程概覽:電腦到底會做哪些事?

  1. 找到資料夾中的 Excel 檔(可用萬用字元過濾,如 *.xlsx)。

  2. 替每個檔決定要讀哪張工作表(第一張,或指定名稱)。

  3. 清理工作表名稱(符號/長度/重名)。

  4. 寫入同一本輸出活頁簿(每個來源檔→一張工作表)。

  5. (選配)封存已處理過的來源檔archive 之類的資料夾。

  6. 寫一頁 INFO 做紀錄:合併時間、來源資料夾、檔案數量。


4) 完整示範(安全改寫版):批次合併 Excel(每檔→一張工作表)

下列程式碼為重新撰寫的教學示範,非你提供的原始碼;可直接使用或依需求調整。


# file: excel_batch_merge.py
# 說明:把資料夾內多個 Excel 檔合併成一個活頁簿,每個來源檔各成一張工作表 # 注意:此示範為全新撰寫,安全可公開 import argparse import logging from pathlib import Path import datetime as dt import re import shutil import pandas as pd # 可支援的副檔名 ALLOW_SUFFIX = {".xlsx", ".xls", ".xlsm", ".xlsb"} def clean_sheet_title(raw: str, used: set) -> str: """清理工作表名稱:替換非法字元、限制長度31、避免重名""" name = re.sub(r'[\[\]\:\*\?\/\\]', '_', (raw or "").strip()) if not name: name = "Sheet" name = name[:31] base = name n = 1 while name in used: suffix = f"_{n}" name = (base[:31 - len(suffix)]) + suffix n += 1 used.add(name) return name def pick_engine(ext: str) -> str | None: ext = ext.lower() if ext in (".xlsx", ".xlsm"): return "openpyxl" # 寫出也會用 openpyxl if ext == ".xls": return "xlrd" # 需 pip install xlrd if ext == ".xlsb": return "pyxlsb" # 需 pip install pyxlsb return None def find_source_files(root: Path, pattern: str | None) -> list[Path]: if pattern: files = sorted(root.glob(pattern)) else: files = [] for suf in ALLOW_SUFFIX: files += root.glob(f"*{suf}") files = sorted(files) # 排除 Excel 的暫存鎖定檔(以 ~$ 開頭) return [p for p in files if not p.name.startswith("~$")] def main(): parser = argparse.ArgumentParser(description="Batch merge Excel files into one workbook (1 file -> 1 sheet).") parser.add_argument("--src", required=True, help="來源資料夾") parser.add_argument("--out", required=True, help="輸出 .xlsx 路徑") parser.add_argument("--filter", default="", help='檔名過濾(例如 "*.xlsx")') parser.add_argument("--mode", choices=["first", "by_name"], default="first", help="讀第一張或指定名稱") parser.add_argument("--sheet", default="", help="當 mode=by_name 時,指定工作表名稱") parser.add_argument("--archive", default="", help="處理後搬到哪個資料夾(選填)") parser.add_argument("--log", default="", help="log 檔路徑(預設輸出到主控台)") args = parser.parse_args() src_dir = Path(args.src).resolve() out_path = Path(args.out).resolve() arc_dir = Path(args.archive).resolve() if args.archive else None # logging 設定 if args.log: Path(args.log).parent.mkdir(parents=True, exist_ok=True) handlers = [logging.FileHandler(args.log, encoding="utf-8")] else: handlers = [logging.StreamHandler()] logging.basicConfig( level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s", handlers=handlers ) logging.info("=== Excel Merge Start ===") logging.info(f"Source: {src_dir}") logging.info(f"Output: {out_path}") if not src_dir.exists(): logging.error("來源資料夾不存在") raise SystemExit(1) files = find_source_files(src_dir, args.filter) if not files: logging.warning("找不到可用的 Excel 檔,結束。") return out_path.parent.mkdir(parents=True, exist_ok=True) used_titles: set[str] = set() merged_count = 0 now = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S") with pd.ExcelWriter(out_path, engine="openpyxl", mode="w") as writer: # 寫 INFO 頁 pd.DataFrame({ "MergedAt": [now], "SourceDir": [str(src_dir)], "FileCount": [len(files)] }).to_excel(writer, index=False, sheet_name=clean_sheet_title("INFO", used_titles)) # 逐檔處理 for f in files: try: eng = pick_engine(f.suffix) if not eng: logging.info(f"跳過不支援的副檔名:{f.name}") continue title = clean_sheet_title(f.stem, used_titles) logging.info(f"讀取({eng}): {f.name}") if args.mode == "first": df = pd.read_excel(f, sheet_name=0, engine=eng) else: if not args.sheet: logging.error("mode=by_name 需要指定 --sheet") continue df = pd.read_excel(f, sheet_name=args.sheet, engine=eng) df.to_excel(writer, index=False, sheet_name=title) merged_count += 1 logging.info(f"寫入:{title}{len(df)} 列)") except Exception as ex: logging.exception(f"處理失敗:{f.name} | {ex}") logging.info(f"完成:共寫入 {merged_count} 張工作表 -> {out_path.name}") # (選配)封存來源檔 if arc_dir and merged_count > 0: arc_dir.mkdir(parents=True, exist_ok=True) for f in files: try: target = arc_dir / f.name if target.exists(): stamp = dt.datetime.now().strftime("%Y%m%d_%H%M%S") target = arc_dir / f"{f.stem}_{stamp}{f.suffix}" shutil.move(str(f), str(target)) logging.info(f"封存:{f.name} -> {target.name}") except Exception as ex: logging.exception(f"封存失敗:{f.name} | {ex}") logging.info("=== Excel Merge End ===") if __name__ == "__main__": main()

如何執行(範例)

# 例1:合併資料夾中所有支援的 Excel,讀「第一張工作表」 python excel_batch_merge.py --src "/data/reports" --out "/data/merged/all.xlsx" # 例2:只合併 .xlsx,且讀名為「Data」的工作表 python excel_batch_merge.py --src "/data/reports" --out "/data/merged/xlsx_only.xlsx" \ --filter "*.xlsx" --mode by_name --sheet "Data" # 例3:成功後把來源檔搬到 archive,並把日誌寫入檔案 python excel_batch_merge.py --src "/data/reports" --out "/data/merged/oct.xlsx" \ --archive "/data/reports/archive" --log "/logs/merge_oct.log"

5) 常見錯誤與排除

  • 讀不到 .xlsx
    → 沒裝 openpyxl。執行:pip install openpyxl

  • 要讀 .xls 舊檔
    → 安裝:pip install xlrd(注意新版本僅支援 .xls

  • 要讀 .xlsb
    → 安裝:pip install pyxlsb

  • 工作表名稱錯誤(含非法字元或超長)
    → 程式裡有 clean_sheet_title 自動處理;若你自行改寫,務必保留這段邏輯。

  • 輸出檔被鎖定(有人開著 Excel)
    → 請關閉目標輸出檔,再重跑。

  • 讀到 ~$ 開頭的暫存檔
    → 示範程式已自動排除;如仍遇到,請關閉相關 Excel 視窗。

  • 記憶體不足(一次載入超大表)
    → 嘗試只選必需欄位 usecols=,或拆檔分批處理,再合併。


6) 進階加值做法

  • 把多個工作表「縱向合併」成同一張表
    目前示範是「一檔一表」。若要改為「把內容接在一起」,可以:讀入後 all_frames.append(df.assign(Source=f.name)),最後 pd.concat(all_frames) 寫成單一工作表

  • 同時支援 CSV/TSV
    若資料來源有 CSV,偵測副檔名後改用 pd.read_csv(),也能整批納入。

  • 自動欄寬/樣式
    寫檔時改用 engine="xlsxwriter" 可做更多格式化(但讀入仍用對應引擎)。

  • 只封存成功寫入者
    現在示範是處理完就封存全部來源檔。若你偏好只搬成功者,可在迴圈中記錄「成功清單」,封存時只處理那些檔案。


7) 常見問答(FAQ)

Q:我不會寫程式,可以用嗎?
A:可以。照著「如何執行」的範例把參數替換成你的路徑即可。遇到錯誤訊息,對照「常見錯誤與排除」逐項檢查。

Q:檔名有中文、工作表也有中文可以嗎?
A:可以。程式會統一用 UTF-8 記錄,工作表名也會自動清理非法字元並避免重名。

Q:可以同時抓多個特定檔名規則嗎?
A:可以多跑幾次、或修改 --filter(例如先合併 *_A.xlsx,再合併 *_B.xlsx 到不同輸出)。

Q:輸出的活頁簿太大怎麼辦?
A:考慮:

  • 分次輸出多本活頁簿(依月份/專案拆分)

  • 僅選必要欄位或先壓縮檔案內容

  • 用 CSV/資料庫管線處理巨量資料


8) 結語

對辦公室日常來說,「把 N 份 Excel 合成 1 份」是高頻需求。把這件事自動化,能省下大量重複勞動並降低手動錯誤。本文用白話流程安全改寫的程式示範帶你完成從零到一的批次合併;之後你也能漸進式加入「縱向合併、CSV 支援、格式化、封存策略」等進階功能,打造更貼近團隊日常的資料整合小工具。

留言

這個網誌中的熱門文章

🔍Vue.js 專案錯誤排查:解決 numericFields is not defined 與合併儲存格邏輯最佳化

🔎EF Core 連 Oracle 出現 ORA-00600 [kpp_concatq:2] 的完整排錯指南(含 EF Core ToString/CultureInfo 錯誤)

🛠【ASP.NET Core + Oracle】解決 ORA-00904 "FALSE": 無效的 ID 錯誤與資料欄位動態插入顯示問題