월별 매출 자료, 부서별 실적표, 설문조사 결과처럼 같은 양식의 엑셀 파일을 하나로 합쳐야 하는 작업이 있습니다.
파일을 하나씩 열고 복사해서 붙여 넣는 대신, 이번 글에서는 파이썬으로 폴더 안의 엑셀 파일을 읽어 하나의 파일로 통합하는 방법을 알아보겠습니다.
예제에서는 다음 작업을 구현합니다.
- 지정한 폴더에서 .xlsx 파일 찾기
- 각 파일의 첫 번째 시트 읽기
- 열 이름이 같은지 확인하기
- 데이터를 위아래로 이어 붙이기
- 각 행이 어느 파일에서 왔는지 기록하기
- 통합 결과를 새로운 엑셀 파일로 저장하기
원본 파일은 수정하지 않도록 구성합니다. 다만 통합 결과 파일인 merged.xlsx는 다시 실행할 때 덮어쓰므로, 이전 결과를 보관하려면 별도로 복사해 두세요.
1. 이번 예제에서 말하는 ‘엑셀 파일 합치기’
엑셀 파일을 합치는 방식은 크게 두 가지로 생각할 수 있습니다.
구분작업 내용예시
| 행 방향으로 합치기 | 같은 구조의 데이터를 아래로 추가 | 1월 매출표 아래에 2월 매출표 추가 |
| 기준 열로 결합하기 | 공통된 번호나 코드에 맞춰 정보를 연결 | 상품코드를 기준으로 매출표에 상품정보 추가 |
이번 글에서는 첫 번째 방식인 ‘행 방향으로 합치기’를 다룹니다. pandas에서는 concat()으로 여러 표를 행 방향으로 연결할 수 있습니다. (pandas.pydata.org)
예제 파일 ① 매출_01월.xlsx
날짜상품수량금액
| 2026-01-05 | 키보드 | 2 | 100000 |
| 2026-01-12 | 마우스 | 3 | 60000 |
예제 파일 ② 매출_02월.xlsx
날짜상품수량금액
| 2026-02-03 | 모니터 | 1 | 250000 |
| 2026-02-10 | 키보드 | 1 | 50000 |
목표는 두 파일의 데이터를 합쳐 총 4행의 데이터가 들어 있는 파일을 만드는 것입니다.
추가로 원본파일 열을 만들어, 각 데이터가 어느 파일에서 왔는지도 남기겠습니다.
2. 실행 전에 확인할 조건
이번 코드는 아래와 같은 입력 파일을 대상으로 작성합니다.
항목예제의 처리 기준
| 파일 형식 | .xlsx |
| 읽을 시트 | 각 파일의 첫 번째 시트 |
| 제목 행 | 첫 번째 행에 열 이름이 있음 |
| 열 이름 | 파일마다 같아야 함 |
| 열 순서 | 달라도 됨. 첫 번째 유효 파일의 순서로 정리 |
| 빈 행 | 모든 값이 비어 있는 행은 제거 |
| 중복 데이터 | 기본적으로 유지 |
| 하위 폴더 | 검색하지 않음 |
입력 파일의 열 이름은 중복되지 않게 준비하고, 코드에서 추가할 원본파일이라는 열 이름은 사용하지 않습니다.
또한 아래처럼 첫 행에 문서 제목이 있고 그 아래에 표가 시작되는 문서는 그대로 사용하지 않는 편이 좋습니다.
1행: 2026년 1월 매출 현황
2행: 빈 행
3행: 날짜 | 상품 | 수량 | 금액
4행: 실제 데이터
이런 파일은 제목 행 위치를 별도로 지정해야 합니다. read_excel()의 기본값은 첫 행을 열 이름으로 사용하며, header 옵션으로 변경할 수 있습니다. (pandas.pydata.org)
3. 필요한 라이브러리 설치하기
파이썬이 설치되어 있다면 터미널 또는 명령 프롬프트에서 다음 명령어를 실행합니다.
python -m pip install pandas openpyxl
이번 예제에서는 다음 두 라이브러리를 사용합니다.
- pandas: 표 데이터를 읽고 합치는 작업
- openpyxl: .xlsx 파일을 읽고 쓰는 데 사용하는 엔진
pandas 공식 문서에서도 openpyxl을 엑셀 파일 읽기·쓰기에 사용하는 선택적 의존성으로 안내하고 있습니다. (pandas.pydata.org)
환경에 따라 파이썬 실행 명령이 python3이라면 다음과 같이 입력하세요.
python3 -m pip install pandas openpyxl
이후 실행 단계에서도 같은 명령을 사용하면 됩니다.
4. 폴더와 예제 파일 준비하기
다음과 같은 구조로 폴더를 준비합니다.
excel_merge/
├─ merge_excel.py
└─ input/
├─ 매출_01월.xlsx
└─ 매출_02월.xlsx
각 항목의 역할은 다음과 같습니다.
- merge_excel.py: 실행할 파이썬 코드
- input: 합칠 엑셀 파일을 넣는 폴더
- 매출_01월.xlsx, 매출_02월.xlsx: 앞에서 소개한 예제 파일
작업이 완료되면 다음 위치에 결과 파일이 생성되도록 만들겠습니다.
excel_merge/
├─ merge_excel.py
├─ merged.xlsx
└─ input/
├─ 매출_01월.xlsx
└─ 매출_02월.xlsx
입력 폴더와 결과 파일 위치를 분리하는 것이 이번 예제의 중요한 설계 포인트입니다. 이렇게 구성하면 다시 실행할 때 이전 결과 파일을 입력 대상으로 읽지 않습니다.
5. 엑셀 파일 여러 개 합치는 전체 코드
아래 코드를 merge_excel.py 파일에 저장하세요.
이 코드는 .py 파일로 저장한 뒤 터미널에서 실행하는 방식을 기준으로 작성했습니다.
from pathlib import Path
import pandas as pd
def main():
# 이 파이썬 파일이 있는 폴더를 기준으로 경로 설정
base_dir = Path(__file__).resolve().parent
input_dir = base_dir / "input"
output_file = base_dir / "merged.xlsx"
if not input_dir.is_dir():
raise FileNotFoundError(
f"입력 폴더가 없습니다. 폴더를 만들어 주세요: {input_dir}"
)
# input 폴더 바로 아래의 xlsx 파일만 선택
# ~$로 시작하는 파일은 처리 대상에서 제외
files = sorted(
path
for path in input_dir.iterdir()
if path.is_file()
and path.suffix.lower() == ".xlsx"
and not path.name.startswith("~$")
)
if not files:
raise FileNotFoundError(
f"합칠 .xlsx 파일이 없습니다: {input_dir}"
)
frames = []
expected_columns = None
for file in files:
# 각 파일의 첫 번째 시트 읽기
df = pd.read_excel(
file,
sheet_name=0,
engine="openpyxl",
)
# 열 이름 양쪽의 공백 제거
df.columns = [str(column).strip() for column in df.columns]
if not df.columns.is_unique:
raise ValueError(
f"{file.name}: 열 이름 정리 후 중복이 발생했습니다."
)
# 모든 값이 비어 있는 행 제거
df = df.dropna(how="all")
if df.empty:
print(f"[건너뜀] {file.name}: 데이터가 없습니다.")
continue
if "원본파일" in df.columns:
raise ValueError(
f"{file.name}: '원본파일'은 코드에서 사용하는 열 이름입니다."
)
# 첫 번째 유효 파일을 기준으로 열 이름 확인
if expected_columns is None:
expected_columns = df.columns.tolist()
else:
missing = sorted(set(expected_columns) - set(df.columns))
extra = sorted(set(df.columns) - set(expected_columns))
if missing or extra:
raise ValueError(
f"{file.name}: 열 이름이 기준 파일과 다릅니다.\n"
f"누락된 열: {missing}\n"
f"추가된 열: {extra}"
)
# 기준 파일의 열 순서에 맞추기
df = df[expected_columns].copy()
# 데이터 출처 기록
df["원본파일"] = file.name
frames.append(df)
print(f"[읽기 완료] {file.name}: {len(df):,}행")
if not frames:
raise ValueError("통합할 데이터가 없습니다.")
# 모든 데이터를 행 방향으로 연결
merged = pd.concat(frames, ignore_index=True)
# 엑셀 한 시트의 행 수 제한 확인: 제목 행 1행 포함
if len(merged) > 1_048_575:
raise ValueError(
"데이터가 엑셀 한 시트의 행 수 제한을 초과했습니다. "
"여러 시트나 파일로 나누어 저장해야 합니다."
)
# 결과 저장: 별도의 인덱스 열은 저장하지 않음
merged.to_excel(
output_file,
sheet_name="통합데이터",
index=False,
engine="openpyxl",
)
print()
print(f"통합에 사용한 파일: {len(frames)}개")
print(f"통합 데이터: {len(merged):,}행")
print(f"저장 위치: {output_file}")
if __name__ == "__main__":
main()
6. 코드 실행하기
터미널에서 merge_excel.py가 있는 폴더로 이동한 뒤 실행합니다.
python merge_excel.py
앞서 소개한 두 예제 파일을 사용했다면, 출력은 다음과 같은 형태가 됩니다.
[읽기 완료] 매출_01월.xlsx: 2행
[읽기 완료] 매출_02월.xlsx: 2행
통합에 사용한 파일: 2개
통합 데이터: 4행
저장 위치: .../excel_merge/merged.xlsx
위 내용은 예제 입력에 대응하는 예상 출력이며, 저장 위치는 컴퓨터 환경에 따라 달라집니다.
생성된 merged.xlsx의 통합데이터 시트를 열면 다음과 같은 표를 확인할 수 있습니다.
날짜상품수량금액원본파일
| 2026-01-05 | 키보드 | 2 | 100000 | 매출_01월.xlsx |
| 2026-01-12 | 마우스 | 3 | 60000 | 매출_01월.xlsx |
| 2026-02-03 | 모니터 | 1 | 250000 | 매출_02월.xlsx |
| 2026-02-10 | 키보드 | 1 | 50000 | 매출_02월.xlsx |
파일을 합쳤다고 바로 끝내기보다, 원본의 데이터 행 수와 결과의 행 수를 비교해보세요. 이번 예제에서는 빈 행을 제외한 원본 데이터가 각각 2행이므로 결과는 4행이어야 합니다.
7. 핵심 코드 이해하기
① 실행 파일을 기준으로 경로 지정하기
base_dir = Path(__file__).resolve().parent
이 코드는 실행 중인 파이썬 파일의 위치를 기준 경로로 사용합니다.
Path의 resolve()는 경로를 해석해 절대 경로로 만들고, parent는 상위 경로를 나타냅니다. 이번 예제에서는 이를 이용해 코드 파일 옆의 input 폴더를 찾도록 구성했습니다. (docs.python.org)
② 엑셀 파일 읽기
df = pd.read_excel(
file,
sheet_name=0,
engine="openpyxl",
)
read_excel()은 엑셀 데이터를 pandas의 표 형태인 DataFrame으로 읽습니다.
여기서 sheet_name=0은 첫 번째 시트를 의미합니다. 두 번째 시트는 1, 특정 이름의 시트는 "매출"처럼 지정할 수 있습니다. (pandas.pydata.org)
③ 완전히 빈 행 제거하기
df = df.dropna(how="all")
how="all"은 해당 행의 모든 값이 결측값일 때만 제거한다는 의미입니다. 일부 셀만 비어 있는 행은 유지합니다. (pandas.pydata.org)
따라서 필수 항목이 누락된 행까지 자동으로 걸러지는 것은 아닙니다. 예를 들어 상품명이 비어 있는 행을 제외하고 싶다면 별도의 검증 규칙을 추가해야 합니다.
④ 파일별 데이터를 한 번에 합치기
merged = pd.concat(frames, ignore_index=True)
frames에 모아 둔 표들을 행 방향으로 연결합니다.
ignore_index=True는 각 표가 가지고 있던 행 인덱스 대신 통합 결과에 새로운 연속 인덱스를 부여하는 옵션입니다. pandas 공식 문서에서도 반복적으로 행을 추가하기보다 데이터를 모아 한 번에 연결하는 방식을 권장합니다. (pandas.pydata.org)
⑤ 불필요한 인덱스 열 없이 저장하기
merged.to_excel(
output_file,
sheet_name="통합데이터",
index=False,
engine="openpyxl",
)
index=False를 지정하면 DataFrame의 행 인덱스를 엑셀의 별도 열로 저장하지 않습니다.
sheet_name으로 결과 시트 이름도 정할 수 있습니다. 같은 경로에 파일이 이미 있으면 기본 쓰기 방식에서는 기존 파일이 덮어써집니다. (pandas.pydata.org)
8. 상황에 맞게 수정하는 방법
특정 이름의 시트만 합치고 싶다면
모든 파일에 매출내역이라는 시트가 있다면, 전체 코드에서 엑셀을 읽는 부분을 다음과 같이 바꾸면 됩니다.
df = pd.read_excel(
file,
sheet_name="매출내역",
engine="openpyxl",
)
이 경우 모든 입력 파일에 해당 시트가 있어야 합니다. 시트 이름 지정은 read_excel()에서 지원하는 기능입니다. (pandas.pydata.org)
제목 행이 엑셀의 세 번째 행에 있다면
모든 파일의 양식이 같고 세 번째 행이 열 이름이라면 다음과 같이 변경합니다.
df = pd.read_excel(
file,
sheet_name=0,
header=2,
engine="openpyxl",
)
header는 0부터 세기 때문에 세 번째 행은 2입니다. 파일마다 제목 행 위치가 다르다면 하나의 설정으로 처리하지 말고, 양식을 먼저 통일하거나 파일별 규칙을 만들어야 합니다. (pandas.pydata.org)
중복 데이터를 제거하고 싶다면
전체 코드의 다음 줄 바로 아래에 중복 제거 코드를 추가합니다.
merged = pd.concat(frames, ignore_index=True)
추가할 코드는 다음과 같습니다.
merged = merged.drop_duplicates(
subset=expected_columns,
ignore_index=True,
)
여기서는 원래 데이터 열만 기준으로 중복을 판단합니다.
그냥 merged.drop_duplicates()를 사용하면 추가된 원본파일 열까지 비교하므로, 내용이 같아도 출처 파일이 다르면 중복으로 처리되지 않을 수 있습니다. subset은 중복 판단에 사용할 열을 지정하는 옵션입니다. (pandas.pydata.org)
다만 값이 같은 두 행이 실제로는 서로 다른 거래일 수도 있습니다. 업무 기준이 명확하지 않다면 중복을 자동으로 삭제하지 않는 편을 권합니다.
9. 실제 자료에 적용할 때 주의할 점
① 열 이름이 다른 파일을 무조건 합치지 않기
예를 들어 한 파일에는 금액, 다른 파일에는 매출액이라는 열이 있을 수 있습니다.
concat()은 기본적으로 열 이름을 기준으로 정렬해 연결하며, 열이 다르면 합집합 형태로 결과를 만들 수 있습니다. 의도와 달리 두 개의 별도 열이 생길 수 있으므로, 이번 코드는 열 이름이 다르면 저장 전에 중단하도록 작성했습니다. (pandas.pydata.org)
두 이름이 같은 뜻이라는 사실을 확인했다면, 엑셀을 읽은 뒤 열 이름을 통일하는 코드를 추가할 수 있습니다.
df = df.rename(columns={"매출액": "금액"})
이 코드는 전체 코드에서 열 이름을 비교하기 전에 넣으세요.
② 상품코드·학번처럼 앞자리 0이 있는 값 확인하기
00123처럼 앞자리 0이 중요한 값은 숫자와 구분해서 다루어야 합니다.
이런 열이 있다면 원본 엑셀에서 해당 값이 텍스트로 저장되어 있는지 먼저 확인하고, 읽을 때도 해당 열을 문자열로 지정하는 방법을 고려하세요. pandas는 열별 자료형을 지정할 수 있습니다. (pandas.pydata.org)
df = pd.read_excel(
file,
sheet_name=0,
dtype={"상품코드": str},
engine="openpyxl",
)
다만 실제 저장값은 숫자 123인데 셀 표시 형식만 00123인 경우, 문자열로 읽는 옵션만으로 표시 형식까지 복원되는 것은 아닙니다. 엑셀의 저장값과 화면에 보이는 서식은 구분해야 합니다. (support.microsoft.com)
③ 원본 서식과 수식 보존 작업으로 사용하지 않기
이번 예제의 목적은 표 데이터를 읽어 새로운 통합표를 만드는 것입니다. 원본 문서의 색상, 테두리, 차트, 인쇄 설정 등을 그대로 복사하는 코드는 아닙니다. (pandas.pydata.org)
수식이 들어 있는 자료도 주의하세요. openpyxl에서는 수식 자체와 파일에 저장된 계산 결과를 구분해서 읽을 수 있으므로, 수식 결과가 중요한 문서는 원본에서 계산·저장 상태를 먼저 확인하는 편이 좋습니다. (openpyxl.readthedocs.io)
수식과 문서 모양을 그대로 보존하는 것이 목적이라면 이번 예제를 그대로 적용하지 않는 것이 좋습니다.
④ 데이터가 매우 크면 저장 방식을 다시 검토하기
엑셀 한 시트의 최대 행 수는 1,048,576행입니다. 이번 코드처럼 제목 행을 한 줄 사용하면 데이터는 최대 1,048,575행까지 들어갈 수 있습니다. (support.microsoft.com)
또한 이번 코드는 읽은 데이터를 메모리에 모아 합치는 구조입니다. 파일이 매우 크다면 다음과 같은 방식으로 설계를 바꿔보세요.
- 결과를 여러 시트나 파일로 분할하기
- 필요한 열과 행만 읽도록 범위 줄이기
- 전체 데이터를 한꺼번에 모으지 않는 처리 방식 검토하기
10. 오류가 발생하면 확인할 사항
설치와 파일 읽기·저장 문제는 각각 pandas의 설치 안내, read_excel(), to_excel() 문서를 함께 참고하면 좋습니다. (pandas.pydata.org)
오류 또는 상황먼저 확인할 내용
| ModuleNotFoundError | 코드를 실행하는 파이썬 환경에 라이브러리를 설치했는지 확인 |
| 입력 폴더가 없다는 메시지 | merge_excel.py 옆에 input 폴더가 있는지 확인 |
| 합칠 파일이 없다는 메시지 | 파일이 input 바로 아래에 있고 확장자가 .xlsx인지 확인 |
| 열 이름이 다르다는 메시지 | 누락·추가된 열 이름을 확인하고 양식 통일 |
| 저장 단계에서 권한 오류 | 결과 파일을 닫은 뒤 저장 폴더의 쓰기 권한 확인 |
| 결과에 합계 행까지 포함됨 | 원본 표 안에 있는 ‘합계’ 행을 별도로 제외할지 검토 |
특히 이 코드는 합계 행, 잘못 입력된 금액, 날짜 오류까지 판단하지는 않습니다. 파일을 합치는 처리와 데이터 내용의 정확성을 검증하는 처리는 별도로 생각해야 합니다.
실행 도중 오류가 발생했다면, 기존 merged.xlsx가 남아 있더라도 이번 작업의 최신 결과라고 판단하지 마세요. 터미널에 완료 메시지가 출력됐는지 확인하는 습관을 권합니다.
마무리: 파일 통합보다 중요한 것은 확인 가능한 자동화
이번 글에서는 파이썬으로 여러 엑셀 파일을 하나로 합치는 예제를 살펴봤습니다.
작업 흐름은 다음과 같습니다.
- 같은 양식의 엑셀 파일을 input 폴더에 넣습니다.
- read_excel()로 각 파일을 읽습니다.
- 열 이름과 빈 데이터를 확인합니다.
- concat()으로 데이터를 연결합니다.
- to_excel()로 결과를 저장합니다.
이번 예제에서는 단순히 파일을 이어 붙이는 것에 더해, 열 구조 확인과 원본 파일명 기록을 넣었습니다.
처음 적용할 때는 원본 전체를 바로 처리하기보다 예제 파일 두 개로 시작해 행 수와 값이 맞는지 확인한 뒤, 실제 자료로 범위를 넓혀보세요.
엑셀 자동화의 목표는 코드를 한 번 실행하는 것이 아니라, 같은 작업을 다시 수행해도 결과를 확인하고 신뢰할 수 있게 만드는 것입니다.
'Programming > Python' 카테고리의 다른 글
| [Python/OpenCV] Near-Duplicate Image Detection #2 (0) | 2020.06.02 |
|---|---|
| [Python/OpenCV] Near-Duplicate Image Detection #1 (0) | 2020.06.02 |
| [Python] List 정렬 프로그램 구현 (0) | 2017.02.06 |
| [Python 3.6] * 찍기 (0) | 2017.02.02 |





