Python으로 여러 Excel 파일을 하나로 합치는 안전한 방법

월별 실적이나 부서별 목록처럼 열 구조가 같은 Excel 파일이 여러 개 있을 때, Python으로 하나의 파일에 행 방향으로 합칠 수 있습니다.

이 글에서는 다음 조건을 포함한 실무형 예제를 사용합니다.

  • 입력 폴더의 .xlsx 파일만 읽기
  • Excel 임시 파일 ~$ 제외
  • 결과 파일을 다시 입력으로 읽지 않기
  • 파일별 출처 열 추가
  • 열 구조가 다른 파일 탐지
  • 빈 파일과 읽기 오류 처리
  • 결과를 merged.xlsx로 저장

핵심 요약
pandas.read_excel()로 각 파일을 DataFrame으로 읽고, pandas.concat()으로 행 방향 결합한 뒤 to_excel()로 저장합니다. .xlsx 파일 처리를 위해 openpyxl을 함께 설치합니다.

1. 패키지 설치

python -m pip install pandas openpyxl

설치 확인:

python -c "import pandas, openpyxl; print(pandas.__version__, openpyxl.__version__)"

2. 폴더 구조

excel-merge/
├─ input/
│  ├─ sales-2026-01.xlsx
│  ├─ sales-2026-02.xlsx
│  └─ sales-2026-03.xlsx
├─ output/
└─ merge_excel.py

각 입력 파일의 첫 번째 시트에 다음과 같은 동일한 열이 있다고 가정합니다.

날짜 | 상품 | 수량 | 금액

3. 전체 코드

from __future__ import annotations

from pathlib import Path
import sys

import pandas as pd


INPUT_DIR = Path("input")
OUTPUT_DIR = Path("output")
OUTPUT_FILE = OUTPUT_DIR / "merged.xlsx"


def find_excel_files(input_dir: Path) -> list[Path]:
    if not input_dir.exists():
        raise FileNotFoundError(f"입력 폴더가 없습니다: {input_dir.resolve()}")

    files = [
        path
        for path in input_dir.glob("*.xlsx")
        if not path.name.startswith("~$")
        and path.resolve() != OUTPUT_FILE.resolve()
    ]
    return sorted(files)


def read_excel_file(path: Path) -> pd.DataFrame:
    frame = pd.read_excel(path, sheet_name=0, engine="openpyxl")

    if frame.empty:
        print(f"[SKIP] 데이터가 없는 파일: {path.name}")
        return frame

    frame = frame.dropna(how="all")
    frame["원본파일"] = path.name
    return frame


def validate_columns(
    frame: pd.DataFrame,
    expected_columns: list[str] | None,
    path: Path,
) -> list[str]:
    current_columns = [
        column for column in frame.columns if column != "원본파일"
    ]

    if expected_columns is None:
        return current_columns

    if current_columns != expected_columns:
        raise ValueError(
            "열 구조가 다릅니다.\n"
            f"- 파일: {path.name}\n"
            f"- 기준: {expected_columns}\n"
            f"- 현재: {current_columns}"
        )

    return expected_columns


def merge_excel_files() -> Path:
    files = find_excel_files(INPUT_DIR)

    if not files:
        raise FileNotFoundError(
            f"합칠 .xlsx 파일이 없습니다: {INPUT_DIR.resolve()}"
        )

    frames: list[pd.DataFrame] = []
    expected_columns: list[str] | None = None

    for path in files:
        print(f"[READ] {path.name}")
        frame = read_excel_file(path)

        if frame.empty:
            continue

        expected_columns = validate_columns(
            frame,
            expected_columns,
            path,
        )
        frames.append(frame)

    if not frames:
        raise ValueError("읽을 수 있는 데이터가 없습니다.")

    merged = pd.concat(frames, ignore_index=True)

    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    merged.to_excel(
        OUTPUT_FILE,
        index=False,
        sheet_name="통합",
        engine="openpyxl",
    )

    print(f"[DONE] {len(merged):,}행 저장")
    print(f"[OUTPUT] {OUTPUT_FILE.resolve()}")
    return OUTPUT_FILE


if __name__ == "__main__":
    try:
        merge_excel_files()
    except Exception as exc:
        print(f"[ERROR] {exc}", file=sys.stderr)
        raise SystemExit(1)

4. 코드 실행

프로젝트 폴더에서 실행합니다.

python merge_excel.py

정상 출력 예:

[READ] sales-2026-01.xlsx
[READ] sales-2026-02.xlsx
[READ] sales-2026-03.xlsx
[DONE] 300행 저장
[OUTPUT] .../output/merged.xlsx

5. 열 순서가 달라도 합치고 싶을 때

위 예제는 열 이름뿐 아니라 순서도 같아야 통과합니다. 열 순서는 달라도 되고 이름만 같으면 되는 경우에는 검증 함수를 집합 비교 방식으로 바꿀 수 있습니다.

if set(current_columns) != set(expected_columns):
    raise ValueError("열 이름 구성이 다릅니다.")

frame = frame[expected_columns + ["원본파일"]]

그러나 이름이 비슷한 다른 열을 자동으로 합치면 데이터가 잘못 정렬될 수 있으므로 초기에는 엄격한 검증이 안전합니다.

6. 여러 시트를 합치는 경우

모든 시트를 읽으려면 다음처럼 sheet_name=None을 사용합니다.

sheets = pd.read_excel(
    path,
    sheet_name=None,
    engine="openpyxl",
)

반환값은 시트명을 키로 하는 딕셔너리입니다.

for sheet_name, frame in sheets.items():
    frame["원본파일"] = path.name
    frame["원본시트"] = sheet_name

7. 자주 발생하는 문제

ModuleNotFoundError: No module named 'openpyxl'

python -m pip install openpyxl

실행 중인 Python과 pip가 다른 환경을 가리킬 수 있으므로 python -m pip 형식을 권장합니다.

결과 파일까지 다시 합쳐지는 문제

결과를 입력 폴더 밖의 output 폴더에 저장하면 가장 단순하게 방지할 수 있습니다.

Excel에서 열어 둔 파일

Excel에서 파일을 열면 ~$파일명.xlsx 임시 파일이 생길 수 있습니다. 예제 코드는 이 파일을 제외합니다.

서식과 수식이 유지되지 않는 문제

pandas 방식은 표 형태의 값을 통합하는 용도입니다. 원본 파일의 복잡한 셀 서식, 차트, 매크로와 수식을 그대로 복제하는 작업에는 적합하지 않습니다.

매크로 파일 .xlsm이나 기존 서식을 보존해야 한다면 openpyxl의 keep_vba 옵션과 워크북 단위 처리를 별도로 설계해야 합니다.

8. 실무에서 추가할 기능

  • 처리 파일 수와 행 수 로그
  • 오류 파일 별도 목록
  • 중복 행 제거
  • 필수 열과 데이터 형식 검사
  • 날짜 형식 통일
  • 대용량 파일의 분할 처리
  • 실행 결과 이메일 또는 Teams 알림

공식 참고 자료

검증일: 2026-07-11