매달 똑같은 양식의 엑셀 파일을 열어 데이터를 정리하고, 셀 서식을 맞추고, 합계를 내는 작업을 반복하고 계신가요? 많은 분들이 이럴 때 VBA 매크로를 떠올리지만, 사실 엑셀 자동화 파이썬을 활용하면 훨씬 유연하고 관리하기 쉬운 방식으로 같은 작업을 처리할 수 있습니다. VBA 문법을 새로 배울 필요 없이, 이미 익숙한 파이썬 문법으로 엑셀 업무를 자동화하는 방법을 소개합니다. 이 글은 2026년 10월에 코드를 다시 돌려 보고 보강했습니다. 처음 올린 예제에서 실제로 돌다가 틀리는 부분 두 곳을 찾아 고쳤고, 행 수별 실행 시간도 함께 적었습니다.
왜 매크로 대신 파이썬일까
VBA 매크로는 엑셀 안에서만 동작하고, 파일이 바뀌거나 여러 엑셀 파일을 동시에 다뤄야 할 때는 한계가 뚜렷합니다. 반면 파이썬은 여러 파일을 한 번에 순회하며 처리할 수 있고, 다른 시스템(이메일 발송, 데이터베이스 연동, 웹 API 호출 등)과도 쉽게 연결됩니다. 코드도 텍스트 파일로 관리되기 때문에 버전 관리나 협업도 훨씬 수월합니다.
필요한 라이브러리
엑셀 자동화에는 보통 openpyxl 또는 pandas를 사용합니다. 서식이나 셀 단위 제어가 필요하면 openpyxl, 대량 데이터 집계나 분석 위주라면 pandas가 더 편리합니다. pandas도 .xlsx를 읽을 때는 내부적으로 openpyxl을 쓰기 때문에 둘 다 설치합니다. 아래 실측은 Python 3.12.10, pandas 3.0.6, openpyxl 3.1.5에서 했습니다.
python -m pip install openpyxl pandas
실전 예제: 여러 파일 합치고 정리하기
매달 지점별로 올라오는 매출 엑셀 파일을 하나로 합쳐서 합계를 내는 작업을 자동화해보겠습니다. 처음 올렸던 코드에서 두 군데를 고친 버전입니다. 무엇이 왜 문제였는지는 아래 “직접 돌려 보니”에 적었습니다.
import pandas as pd
from pathlib import Path
SOURCE_DIR = Path("./sales_reports")
# 결과 파일은 원본 폴더 밖에 둡니다. 안에 두면 다음 달 실행 때 같이 합쳐집니다.
OUTPUT_FILE = Path("./monthly_summary.xlsx")
def merge_sales_files(source_dir: Path) -> pd.DataFrame:
all_data = []
for file in sorted(source_dir.glob("*.xlsx")):
# 엑셀이 파일을 열고 있으면 생기는 ~$ 잠금 파일, 그리고 결과 파일 자체는 건너뜁니다.
if file.name.startswith("~$") or file.resolve() == OUTPUT_FILE.resolve():
continue
df = pd.read_excel(file)
df["지점"] = file.stem
all_data.append(df)
return pd.concat(all_data, ignore_index=True)
def summarize(df: pd.DataFrame) -> pd.DataFrame:
return df.groupby("지점")["매출액"].sum().reset_index()
if __name__ == "__main__":
merged = merge_sales_files(SOURCE_DIR)
summary = summarize(merged)
summary.to_excel(OUTPUT_FILE, index=False)
print("월간 요약 파일이 생성되었습니다:", OUTPUT_FILE)

이 스크립트는 지정한 폴더 안의 모든 엑셀 파일을 읽어 하나로 합친 뒤, 지점별 매출 합계를 계산해 새 엑셀 파일로 저장합니다. 파일이 늘어나면 시간이 얼마나 늘어나는지는 아래 표에 실제로 재 둔 값이 있습니다.
서식까지 다루고 싶다면
단순 집계를 넘어 셀 색상, 굵은 글씨, 조건부 서식까지 적용하고 싶다면 openpyxl을 함께 사용하면 됩니다. 예를 들어 100만 원 미만인 매출 셀에 색을 입히는 코드입니다. 숫자인지 먼저 확인하는 줄이 들어간 것이 원래 버전과 다른 점입니다.
from openpyxl import load_workbook
from openpyxl.styles import PatternFill
wb = load_workbook(OUTPUT_FILE)
ws = wb.active
fill = PatternFill(start_color="FFC7CE", end_color="FFC7CE", fill_type="solid")
for (cell,) in ws.iter_rows(min_row=2, min_col=2, max_col=2):
# 빈 칸·"집계중" 같은 글자가 섞여 있어도 멈추지 않게 숫자만 비교합니다.
if isinstance(cell.value, (int, float)) and cell.value < 1000000:
cell.fill = fill
wb.save(OUTPUT_FILE)
직접 돌려 보니: 행 수별 실행 시간
지점 파일 10개를 만들고(열은 날짜·품목·매출액 세 개), 파일당 행 수를 100행부터 5만 행까지 늘려 가며 위 병합 코드를 그대로 돌렸습니다. 노트북 한 대에서 잰 값이라 절대값보다는 늘어나는 비율을 봐 주세요.
| 파일당 행 | 전체 행 | 병합(읽기+합치기) | 집계+저장 | 파일 10개 크기 |
|---|---|---|---|---|
| 100 | 1,000 | 0.07초 | 0.010초 | 0.1MB |
| 1,000 | 10,000 | 0.33초 | 0.008초 | 0.2MB |
| 10,000 | 100,000 | 4.79초 | 0.026초 | 2.0MB |
| 50,000 | 500,000 | 19.96초 | 0.043초 | 9.6MB |
시간은 거의 전부 엑셀 파일을 읽는 데 씁니다. 50만 행을 합친 뒤 지점별로 더하고 결과를 저장하는 데는 0.04초밖에 걸리지 않았습니다. 지점 파일이 수십 개, 각각 수천 행 정도라면 몇 초 안에 끝나니 신경 쓸 일이 없습니다. 문제는 한 파일에 수만 행씩 들어 있을 때입니다.
읽기가 느리면 엔진을 바꿔 볼 수 있습니다. python-calamine을 설치하고 pd.read_excel(file, engine="calamine")로 읽게 하자, 같은 50만 행이 openpyxl 엔진에서는 21.01초·20.77초였는데 calamine에서는 6.21초, 두 번째 실행은 3.03초였습니다. 설치하지 않은 채 엔진 이름만 바꾸면 ImportError: `Import python-calamine` failed가 나니 먼저 python -m pip install python-calamine을 해야 합니다. 다만 calamine은 읽기 전용이라 저장과 서식 작업은 여전히 openpyxl이 합니다.
직접 돌려 보니: 처음 코드에서 틀렸던 곳
① 결과 파일이 다음 실행 때 데이터로 섞였습니다. 처음 글에는 결과 파일 위치를 따로 정하지 않았습니다. 그래서 결과를 원본 폴더 안에 저장하고 다시 돌려 봤더니, 지점 3개·30행이어야 할 데이터가 33행이 됐고 지점 목록에 monthly_summary라는 지점이 생겼습니다. 지난번 요약 3행이 원본처럼 합쳐진 것입니다. 매달 같은 폴더에서 돌리는 자동화라면 합계가 조용히 부풀어 오릅니다. 고친 버전은 결과 파일을 폴더 밖에 두고, 혹시 안에 있어도 건너뜁니다. 두 번 연속 돌려도 30행이 그대로였습니다.
② 엑셀을 열어 둔 채 실행하면 멈췄습니다. 엑셀은 파일을 열면 같은 폴더에 ~$지점01.xlsx 같은 숨은 잠금 파일을 만듭니다. 이 파일도 *.xlsx에 걸리기 때문에, 폴더에 잠금 파일을 두고 실행하자 ValueError: Excel file format cannot be determined, you must specify an engine manually.로 멈췄습니다. 오류 문구만 보면 엔진 문제로 보여서 원인을 찾기 어렵습니다. 이름이 ~$로 시작하는 파일을 건너뛰자 통과했습니다.
③ 서식 코드는 글자가 섞인 칸에서 죽었습니다. 매출액 칸에 500000, 빈 칸, “집계중”, 2000000을 넣고 원래 서식 코드를 돌리자 TypeError: '<' not supported between instances of 'str' and 'int'가 났습니다. 빈 칸은 if cell.value에서 걸러지지만 글자는 그대로 숫자와 비교되기 때문입니다. 숫자인지 확인하도록 바꾼 뒤에는 500000 한 칸만 칠해지고 끝났습니다.
수식 칸을 읽으면 값이 아니라 수식이 나옵니다
C2 칸에 =A2+B2를 넣은 파일을 openpyxl로 만들어 다시 읽어 봤습니다. 결과는 다음과 같았습니다.
load_workbook(p)["Sheet"]["C2"].value # '=A2+B2'
load_workbook(p, data_only=True)["Sheet"]["C2"].value # None
pd.read_excel(p).iloc[0].tolist() # [10.0, 20.0, nan]
data_only=True를 줘도 값이 None입니다. 수식의 계산 결과는 엑셀 프로그램이 파일을 저장할 때 함께 적어 두는 것이라, 파이썬으로만 만든 파일에는 그 값이 아예 없습니다. 사람이 엑셀에서 열어 저장한 파일이면 값이 나옵니다. 그래서 파이썬이 만든 중간 파일을 다시 읽어 계산하는 구조라면 수식을 넣지 말고, 계산은 pandas에서 끝내 값으로 저장하는 편이 안전합니다.
자동 실행까지 연결하기
이렇게 만든 스크립트는 윈도우 작업 스케줄러와 연결해 매달 정해진 날짜에 자동으로 실행되도록 설정할 수 있습니다. 이때 위 ①번 문제가 특히 중요합니다. 사람이 손으로 돌릴 때는 결과 파일이 이상하면 바로 눈치채지만, 무인 실행에서는 몇 달 동안 부풀린 합계가 메일로 나갈 수 있습니다. 실행이 끝나면 합친 행 수를 로그에 남겨 두면 이상을 빨리 알아챌 수 있습니다.
마무리
VBA 매크로에 익숙하지 않거나, 엑셀 파일 하나를 넘어 여러 시스템과 연결된 자동화가 필요하다면 파이썬이 좋은 선택입니다. 다만 예제를 그대로 가져다 쓰기 전에 결과 파일 위치, 잠금 파일, 글자가 섞인 칸 이 세 가지만 자기 폴더에서 확인해 보세요. 이번에 다시 돌려 보니 처음 코드가 틀렸던 곳이 정확히 이 세 군데였습니다.