📊零基礎也能做!把多個 Excel 自動合併並打包成 Windows .exe:完整圖解、CMD/PowerShell 差異、.bat 一鍵執行與常見錯誤排除
- 取得連結
- X
- 以電子郵件傳送
- 其他應用程式
內容(Content)
為什麼寫這篇?
在公司或專案中,我們常會收到一堆散落各處的 Excel 檔案:每位同事一份、每台產線一份、每天一份……手動合併既耗時又容易出錯。本文用非工程師也能看懂的方式,帶你做一個「一鍵合併 Excel」的小工具,最後再把它打包成 Windows 可執行檔(.exe),同事就算沒裝 Python 也能用。
你將學到:
-
怎麼把多個 Excel 自動合併成一本,每個來源檔一張工作表
-
CMD vs PowerShell 的輸入差異(為什麼同一串指令,有時候會報錯?)
-
怎麼把 Python 腳本打包成 .exe(PyInstaller)
-
用 .bat 一鍵執行,雙擊就跑
-
常見錯誤與快速排除
注意:本文的所有範例碼都是全新撰寫,與你先前使用的版本不同,避免任何隱私或授權疑慮。
成品長這樣(快速總覽)
-
放檔案到
in/資料夾 → 按一下run.bat→ 在out/merged.xlsx得到彙整檔 -
每個來源 Excel 會變成輸出活頁簿中的不同工作表
-
支援副檔名:
.xlsx/.xlsm/.xls/.xlsb -
支援過濾(只收
*.xlsx等) -
可將日誌寫到檔案,方便追查
1. 準備環境(一次搞定)
有網路的情況下,用最新版 Python 3.12 與 PyInstaller 會最省事。
:: 建議在新資料夾中執行 py -3.12 -m venv .venv .\.venv\Scripts\activate python -m pip install -U pip python -m pip install pandas openpyxl xlrd pyxlsb pyinstaller
2. 合併 Excel 的 Python 腳本(全新版本)
檔名:excel_collector.py(你可以自取)
這支會把 in/ 內的
Excel 檔合併到
out/merged.xlsx,每個來源檔成為一張工作表;未指定參數時,預設以程式所在位置為基準。
#!/usr/bin/env python3
"""
ExcelCollector:把多個 Excel 合併成一本,每個來源檔一張工作表。
(全新示例碼,避免與任何既有版本重複)
"""
from __future__ import annotations
import argparse, sys, re, logging
from pathlib import Path
from datetime import datetime
import pandas as pd
# 不合法的工作表字元
_ILLEGAL = r'[:\\/?*\[\]]'
# 各副檔名對應的 pandas 引擎
_ENGINES = {'.xlsx': 'openpyxl', '.xlsm': 'openpyxl', '.xls': 'xlrd', '.xlsb': 'pyxlsb'}
def _prog_dir() -> Path:
"""回傳程式所在目錄(打包後為 exe 所在目錄)"""
if getattr(sys, 'frozen', False):
return Path(sys.executable).resolve().parent
return Path(__file__).resolve().parent
def _clean_sheet(name: str, used: set[str]) -> str:
"""清理 sheet 名稱,限制 31 字且去重"""
s = re.sub(_ILLEGAL, '_', name).strip() or 'Sheet'
s = s[:31]
base, n = s, 1
while s in used:
suffix = f'_{n}'
s = (base[:31 - len(suffix)]) + suffix
n += 1
used.add(s)
return s
def _list_sources(root: Path, pattern: str | None) -> list[Path]:
exts = {'.xlsx', '.xlsm', '.xls', '.xlsb'}
if pattern:
files = sorted([p for p in root.glob(pattern) if not p.name.startswith('~$')])
else:
items = []
for ext in exts:
items.extend(root.glob(f'*{ext}'))
files = sorted([p for p in items if not p.name.startswith('~$')])
return files
def _engine_for(p: Path) -> str | None:
return _ENGINES.get(p.suffix.lower())
def _parse(argv):
ap = argparse.ArgumentParser(description='ExcelCollector:合併多個 Excel 到單一本 .xlsx')
ap.add_argument('--input', help='來源資料夾(預設:程式同層的 ./in)')
ap.add_argument('--output', help='輸出活頁簿路徑 .xlsx(預設:./out/merged.xlsx)')
ap.add_argument('--pattern', help='檔名過濾,例如:*.xlsx 或 *2025*.xlsb')
ap.add_argument('--sheet', help='要讀的工作表名稱;未提供則讀第一張')
ap.add_argument('--logfile', help='日誌檔路徑;未提供則輸出到主控台')
return ap.parse_args(argv)
def _run(args) -> int:
base = _prog_dir()
src = (Path(args.input) if args.input else base / 'in').resolve()
dst = (Path(args.output) if args.output else base / 'out' / 'merged.xlsx').resolve()
src.mkdir(parents=True, exist_ok=True)
dst.parent.mkdir(parents=True, exist_ok=True)
logging.info('Source folder: %s', src)
files = _list_sources(src, args.pattern)
if not files:
logging.warning('未找到來源檔,請把 Excel 放到:%s', src)
return 0
used = set()
with pd.ExcelWriter(dst, engine='openpyxl', mode='w') as writer:
meta = pd.DataFrame([{
'MergedAt': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'SourceDir': str(src),
'FileCount': len(files),
}])
meta.to_excel(writer, index=False, sheet_name=_clean_sheet('INFO', used))
for f in files:
eng = _engine_for(f)
if eng is None:
logging.info('跳過不支援的副檔名:%s', f.name)
continue
try:
target_sheet = args.sheet if args.sheet else 0
df = pd.read_excel(f, sheet_name=target_sheet, engine=eng)
df.to_excel(writer, index=False, sheet_name=_clean_sheet(f.stem, used))
logging.info('合併完成:%s(%d 列)', f.name, len(df))
except Exception as e:
logging.exception('讀取失敗 %s:%s', f.name, e)
logging.info('輸出完成:%s', dst)
return 0
def main(argv=None):
args = _parse(argv or sys.argv[1:])
handlers = [logging.StreamHandler()]
if args.logfile:
Path(args.logfile).parent.mkdir(parents=True, exist_ok=True)
handlers = [logging.FileHandler(args.logfile, encoding='utf-8')]
logging.basicConfig(level=logging.INFO, format='%(asctime)s | %(levelname)s | %(message)s', handlers=handlers)
return _run(args)
if __name__ == '__main__':
raise SystemExit(main())
怎麼用?
# 看說明
python excel_collector.py --help
# 最簡單:用預設的 ./in -> ./out/merged.xlsx
python excel_collector.py
# 只收 .xlsx,並指定工作表名稱
python excel_collector.py --pattern "*.xlsx" --sheet "報表"
3. 打包成 .exe(PyInstaller)
讓同事不裝 Python 也能用
:: 仍在虛擬環境中 (.venv): pyinstaller -F -n ExcelCollector ^ --hidden-import openpyxl ^ --hidden-import xlrd ^ --hidden-import pyxlsb ^ excel_collector.py
-
成品在:
dist/ExcelCollector.exe -
測試:
.\dist\ExcelCollector.exe --help .\dist\ExcelCollector.exe --pattern "*.xls*" --sheet "報表"
--hidden-import?
因為
pandas
會動態載入讀檔引擎,打包時容易漏掉,手動指定最穩妥。
4. CMD 與 PowerShell 的差異(超多人在這裡卡關)
-
執行目前資料夾的程式
-
CMD:
ExcelCollector.exe或.\ExcelCollector.exe -
PowerShell:一定要
.\ExcelCollector.exe
-
-
換行符號
-
CMD:用
^續行 -
PowerShell:用 反引號「`」續行,或乾脆打一行
-
-
有疑問先試:
.\ExcelCollector.exe --help
5. 一鍵執行的 .bat(全新寫法)
檔名:run.bat,放在
ExcelCollector.exe
同資料夾
@echo off setlocal EnableExtensions REM 以 bat 所在資料夾為基準(支援中文/空白路徑) set "ROOT=%~dp0" set "BIN=%ROOT%ExcelCollector.exe" if not exist "%BIN%" ( echo [ERROR] 找不到執行檔:%BIN% pause & exit /b 1 ) REM 預設路徑 set "SRC=%ROOT%in" set "DST=%ROOT%out\merged.xlsx" set "LOG=%ROOT%logs\run.log" REM 確保父資料夾存在 for %%F in ("%DST%") do if not exist "%%~dpF" mkdir "%%~dpF" 2>nul for %%F in ("%LOG%") do if not exist "%%~dpF" mkdir "%%~dpF" 2>nul if not exist "%SRC%" mkdir "%SRC%" 2>nul REM 執行(想改條件,這裡改 pattern、sheet 等) "%BIN%" --input "%SRC%" --output "%DST%" --pattern "*.xls*" --logfile "%LOG%" set "RC=%ERRORLEVEL%" if not "%RC%"=="0" echo [ERROR] 程式回傳碼:%RC% pause exit /b %RC%
用法:把 Excel 放到
in/,雙擊
run.bat,結果會出現在
out/merged.xlsx。
6. 也可以用 PowerShell 啟動(可選)
檔名:run.ps1
若遇到「執行原則」限制,可在該視窗暫時放行:
$exe = Join-Path $PSScriptRoot 'ExcelCollector.exe' $src = Join-Path $PSScriptRoot 'in' $dst = Join-Path $PSScriptRoot 'out\merged.xlsx' $log = Join-Path $PSScriptRoot 'logs\run.log' New-Item -ItemType Directory -Force -Path (Split-Path $dst) | Out-Null New-Item -ItemType Directory -Force -Path (Split-Path $log) | Out-Null New-Item -ItemType Directory -Force -Path $src | Out-Null & $exe --input $src --output $dst --pattern "*.xls*" --logfile $log
Set-ExecutionPolicy -Scope Process -ExecutionPolicy
Bypass
7. 常見錯誤對照表
| 症狀/訊息 | 可能原因 | 快速解法 |
|---|---|---|
... is not recognized as an internal or external
command(找不到 .exe)
|
未加
.\(PowerShell)或不在該資料夾
|
cd 到
.exe 所在資料夾,PowerShell 用
.\ExcelCollector.exe
|
the following arguments are required
|
少帶必要參數 |
先跑
--help
看有哪些參數;或用
run.bat
|
讀
.xls
失敗
|
沒有
xlrd
或版本不符
|
python -m pip install xlrd,或把檔案另存為
.xlsx
|
讀
.xlsb
失敗
|
沒有
pyxlsb
|
python -m pip install pyxlsb
|
| 中文/空白路徑出錯 | 沒加引號 |
路徑一律加雙引號
"..."
|
| 合併後工作表名稱重複/非法 | Excel 限制(31字/非法字元) |
程式已自動處理:替換非法字元、超長截斷、撞名加尾碼
_1/_2/...
|
| 執行檔被防毒誤判 | 未簽章的單檔 .exe |
改用資料夾模式(不加
-F),或對 .exe 進行簽章(正式發佈建議)
|
8. SEO 重點關鍵字(內容已自然置入)
-
Excel 合併、自動化合併 Excel、Windows .exe、PyInstaller、CMD 與 PowerShell 差異、批次檔 .bat、新手也能做、企業內部工具、自動資料整合、pandas openpyxl xlrd pyxlsb
結語
把重複又枯燥的工作自動化,是工程師能帶來的直接價值。這篇以最少門檻的方式,從「合併 Excel 腳本」一路走到「打包 .exe」「.bat 一鍵執行」,同事端也能輕鬆使用。你可以在此基礎上再擴充:像是把所有資料合併到同一張表、加入檔名/日期欄、甚至加上 GUI 介面。若你想把這個工具做成公司內部的標準流程,我也可以把腳本改成無參數即自動使用預設路徑+互動式問答的版本,並附上更完整的錯誤訊息與日誌旋轉機制。
- 取得連結
- X
- 以電子郵件傳送
- 其他應用程式
留言
張貼留言