Application/Python

[Python] 09. 파이썬 미니 프로젝트: 월별 매출 정리 자동화

devsalix 2026. 7. 1. 10:09
728x90

파이썬 미니 프로젝트: 월별 매출 정리 자동화

매달 말이 되면 각 지점에서 엑셀 파일이 하나둘 메일로 날아오잖아요. 2025_01.xlsx, 2025_02.xlsx... 이걸 하나씩 열어서 복붙으로 합치고, 지점별로 SUMIF 걸고, 헤더에 색칠하고, 막대 차트까지 그려서 보고서를 만들던 그 반나절.

솔직히 이거, 매번 똑같은 작업이잖아요. 그런데도 파일이 12개면 12번 반복해야 하고요.

오늘은 그걸 파일 하나 실행으로 끝내볼 거예요. 그것도 지금까지 배운 것들만 조립해서요.


뭘 만드는 건지 먼저 그림부터

입력은 매출/ 폴더에 쌓인 월별(또는 지점별) 엑셀 파일들이에요. 각 파일은 날짜 / 지점 / 매출 이렇게 3열이고, 1행은 헤더라고 할게요.

출력은 이런 보고서 하나입니다. 지점별 매출 합계가 표로 정리되고, 헤더엔 색이 들어가고, 옆엔 막대 차트가 붙은 결과/월별매출_보고서.xlsx.

전체 흐름은 이래요.

파일 다 읽기 → 한 통에 모으기 → 지점별로 합계 내기 → 서식+차트 보고서로 저장

그리고 여기서 진짜 새로 배우는 건 딱 하나예요. 세 번째 "지점별로 합계 내기". 나머지는 전부 05부터 08까지 이미 해본 것들이거든요. 그래서 아는 건 빠르게 훑고, 새 개념 하나에만 천천히 머물게요.


Step 1 — 파일 목록 가져오기 (06 복습)

 

[Python] 06. 파이썬으로 여러 엑셀 파일 하나로 합치기

파이썬으로 여러 엑셀 파일 하나로 합치기월말 되면 지점별로, 혹은 월별로 엑셀 파일이 우르르 쌓이잖아요. 1월.xlsx, 2월.xlsx... 아니면 강남점, 분당점, 일산점... 이게 한두 개면 모르겠는데 12개

devsalix.tistory.com

 

이건 06에서 여러 파일 합칠 때 했던 그대로예요.

from pathlib import Path

for file in sorted(Path("매출").glob("*.xlsx")):  # 이름순으로 정렬
    if file.name.startswith("~$"):               # 엑셀 임시파일은 제외
        continue
    print(file.name)                             # 2025_01.xlsx ...

 

glob("*.xlsx")가 폴더 안 엑셀 파일을 다 찾아주고요. ~$로 시작하는 건 엑셀이 파일 열 때 만드는 임시 파일이라 걸러줍니다. 이 부분 기억나시죠? 넘어갈게요.


Step 2 — 데이터 다 읽어서 합치기 (05·06 복습)

 

[Python] 05. 파이썬 엑셀 자동화, openpyxl로 시작하기

파이썬 엑셀 자동화, openpyxl로 시작하기엑셀 파일에 데이터를 일일이 옮겨 적다가 야근해 보신 적, 다들 한 번쯤은 있으시잖아요. 거래처에서 보내준 PDF 보면서 셀에 숫자 옮기고, 월말 되면 부

devsalix.tistory.com

 

파일을 열고 행을 한 줄씩 꺼내는 것도 이미 배운 거예요. 리스트 하나에 전부 쏟아붓기만 하면 됩니다.

from pathlib import Path
from openpyxl import load_workbook

def read_all_files(folder):
    rows = []                                          # 모든 행을 담을 리스트
    for file in sorted(Path(folder).glob("*.xlsx")):
        if file.name.startswith("~$"):
            continue
        ws = load_workbook(file).active
        for row in ws.iter_rows(min_row=2, values_only=True):  # 헤더(1행) 건너뜀
            if not any(row):                           # 완전 빈 행은 건너뜀
                continue
            rows.append(row)                           # (날짜, 지점, 매출)
    return rows

 

min_row=2로 헤더를 건너뛰고, values_only=True로 셀 값만 튜플로 받아요. 굳이 함수로 묶어둔 이유가 있는데, 그건 조금 뒤에 조립할 때 자연스럽게 드러나거든요.


핵심은 딱 하나, dict로 지점별 합계 내기

자, 여기가 오늘의 심장이에요.

엑셀에선 피벗테이블이나 SUMIF로 "지점별로 묶어서 합치기"를 했잖아요. 이걸 코드로 어떻게 하냐면요, 저금통을 떠올리시면 돼요.

책상에 지점 이름표가 붙은 저금통들을 늘어놓는다고 상상해보세요. 영수증(데이터)을 한 장씩 보면서 "이건 강남점 거네" 하고 강남 저금통에 금액을 넣어요. 처음 보는 지점이면? 빈 저금통을 새로 하나 꺼내 놓고 거기서부터 시작하고요. 다 넣고 나면 저금통마다 총액이 딱 모이겠죠.

이 저금통이 파이썬에선 dict(딕셔너리)예요. {지점: 합계} 구조요.

데이터 = [("강남", 150000), ("판교", 90000), ("강남", 200000)]  # (지점, 매출)

합계 = {}                                    # 텅 빈 저금통 세트
for 지점, 매출 in 데이터:
    합계[지점] = 합계.get(지점, 0) + 매출     # 처음 보면 0에서 시작, 있으면 더함

print(합계)   # {'강남': 350000, '판교': 90000}

# 실행 결과:
# {'강남': 350000, '판교': 90000}

 

여기서 합계.get(지점, 0) 이 한 줄이 마법이에요.

.get(지점, 0)은 "이 지점 저금통이 이미 있으면 그 값을 꺼내오고, 처음 보는 지점이면 그냥 0으로 쳐라"는 뜻이거든요. 뒤의 0이 바로 "빈 저금통은 0원부터 시작"이라는 의미예요. 그래서 if 지점이 이미 있으면... 같은 조건문을 따로 안 써도 한 줄로 끝나요.

이 패턴, 외워두시면 평생 씁니다. 정말로요.

실제 프로젝트에 넣으면 이렇게 되고요.

def summarize(rows):
    합계 = {}
    for 날짜, 지점, 매출 in rows:
        if 매출 is None:                                # 빈 매출은 건너뜀
            continue
        매출 = int(str(매출).replace(",", ""))          # 문자열·쉼표 방어 후 숫자로
        합계[지점] = 합계.get(지점, 0) + 매출
    return 합계                                         # {'강남점': 3500000, ...}

 

중간의 int(str(매출).replace(",", ""))는 왜 있는지 밑에서 따로 얘기할게요. 지금은 그냥 "숫자로 안전하게 바꾸는 부분"이라고만 보시면 돼요.


서식과 차트로 보고서답게 (07 복습)

 

[Python] 07. 파이썬으로 엑셀 자동 보고서 만들기 (서식과 차트)

파이썬으로 엑셀 자동 보고서 만들기 (서식과 차트)지난번에 흩어진 엑셀 파일 여러 개를 하나로 합쳤잖아요. 그거 열어보셨어요?저는 막상 합쳐놓고 좀 민망했거든요. 데이터는 다 들어왔는데,

devsalix.tistory.com

 

집계된 dict를 이제 예쁜 보고서로 펼쳐줄 차례예요. 헤더 색칠, 천 단위 콤마, 총합계 행, 막대 차트... 전부 07에서 해본 거라 코드만 툭 놓을게요.

from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
from openpyxl.chart import BarChart, Reference

def make_report(summary, out_path):
    wb = Workbook()
    ws = wb.active
    ws.title = "매출요약"
    ws.append(["지점", "매출합계"])                     # 헤더 행

    for 지점, 금액 in summary.items():                  # dict를 표로 펼치기
        ws.append([지점, 금액])

    fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
    for cell in ws[1]:                                  # 헤더에 파란 배경 + 흰 글씨
        cell.font = Font(bold=True, color="FFFFFF")
        cell.fill = fill
        cell.alignment = Alignment(horizontal="center")

    last = ws.max_row
    for r in range(2, last + 1):                        # 매출 열에 천 단위 콤마
        ws.cell(row=r, column=2).number_format = "#,##0"

    ws.cell(row=last + 1, column=1, value="총합계").font = Font(bold=True)
    total = ws.cell(row=last + 1, column=2, value=f"=SUM(B2:B{last})")  # 합계 수식
    total.font = Font(bold=True)
    total.number_format = "#,##0"

    chart = BarChart()                                  # 막대 차트
    chart.title = "지점별 매출"
    data = Reference(ws, min_col=2, min_row=1, max_row=last)
    cats = Reference(ws, min_col=1, min_row=2, max_row=last)
    chart.add_data(data, titles_from_data=True)
    chart.set_categories(cats)
    ws.add_chart(chart, "D2")

    wb.save(out_path)

 

PatternFill로 헤더 색, number_format으로 콤마, BarChart로 차트. 07의 그 코드 거의 그대로라 새로울 게 없으실 거예요. 이미 손에 익은 부품이라 그냥 가져다 쓰면 되는 거죠.


결과 저장은 꼭 다른 폴더에 (06 그 함정 또 나와요)

여기서 06에서 강조했던 함정 하나만 다시 짚을게요.

결과 보고서를 입력 폴더인 매출/에 저장하면 안 돼요. 왜냐면 다음에 다시 실행할 때 glob("*.xlsx")가 그 결과 파일까지 읽어버리거든요. 그럼 데이터가 실행할 때마다 눈덩이처럼 불어나요.

그래서 입력은 매출/, 출력은 결과/로 폴더를 나눠요.


이제 조립하기 (엑셀 자동화 실습의 클라이맥스)

아까 함수를 세 개로 쪼갠 이유가 지금 나와요. read_all_files(읽기), summarize(집계), make_report(보고서). 이렇게 부품별로 나눠두면 메인은 딱 부품을 순서대로 부르기만 하면 되거든요.

from pathlib import Path

INPUT_FOLDER = "매출"                                # 입력 폴더
OUTPUT_FILE = Path("결과") / "월별매출_보고서.xlsx"   # 출력은 다른 폴더
Path("결과").mkdir(exist_ok=True)                    # 결과 폴더 없으면 생성

rows = read_all_files(INPUT_FOLDER)                  # 1) 다 읽어서 합치기
summary = summarize(rows)                            # 2) 지점별 집계
make_report(summary, OUTPUT_FILE)                    # 3) 보고서 생성
print(f"완료! {OUTPUT_FILE} 열어보세요")

# 실행 결과:
# 완료! 결과\월별매출_보고서.xlsx 열어보세요

 

보이시죠? 핵심은 딱 세 줄이에요. 읽고, 집계하고, 보고서 만들고. 반나절 야근이 이 세 줄로 압축된 거예요.


깔끔한 예제로는 되는데 회사 파일 넣으면 터져요

여기까지 잘 따라오셨으면 한 가지 경고를 드릴게요. 직접 만든 깨끗한 예제 파일로는 잘 돌아가다가, 실제 회사 파일을 넣는 순간 갑자기 에러가 나서 당황하실 거예요. 저도 그랬거든요.

제일 흔한 두 가지예요.

빈 칸에서 터지는 에러

TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

 

매출 칸이 비어 있으면 파이썬은 그걸 None으로 읽어요. None + 숫자는 안 되니까 터지죠. 그래서 위 summarizeif 매출 is None: continue를 넣어둔 거예요.

숫자가 문자열로 저장돼 있는 경우

엑셀 셀에 매출이 1500000(숫자)이 아니라 "1,500,000"(쉼표 낀 문자열)로 들어 있는 경우가 진짜 많아요. 그럼 이런 에러가 나요.

ValueError: invalid literal for int() with base 10: '1,500,000'

 

쉼표 때문에 int()가 못 바꾸는 거예요. 그래서 int(str(매출).replace(",", ""))처럼 쉼표를 먼저 지우고 숫자로 바꿔줘야 해요. 위 코드에 이미 넣어뒀고요.

이 두 방어 코드, 실전 파일 다룰 땐 거의 필수라고 보시면 됩니다.


보너스: 구글 시트에도 기록하기 (08 복습)

 

[Python] 08. 파이썬으로 구글 시트 자동 연동하기 (gspread)

파이썬으로 구글 시트 자동 연동하기 (gspread)팀이 같이 쓰는 구글 시트에 매일 매출이나 실적을 손으로 옮겨 적어본 적 있으세요? 야근하면서 숫자 몇십 개를 시트에 또각또각 입력하다 보면 "이

devsalix.tistory.com

 

08에서 구글 시트 연결까지 해두셨다면, 결과를 시트에 한 줄씩 쌓는 것도 금방이에요.

import gspread

gc = gspread.service_account(filename="credentials.json")  # 08에서 만든 키
ws = gc.open("월별매출기록").sheet1

for 지점, 금액 in summary.items():
    ws.append_row([지점, 금액])                            # 한 줄씩 추가

 

08의 인증 세팅(서비스 계정 이메일을 시트에 공유)이 안 돼 있으면 403 에러가 나니까, 아직 안 보셨으면 이 보너스는 건너뛰셔도 돼요.


직접 해볼 과제

  1. 같은 양식(날짜/지점/매출)으로 엑셀 파일 2~3개를 대충 만들어서 매출/ 폴더에 넣어보세요. 그리고 위 메인 코드를 실행해서 결과/월별매출_보고서.xlsx가 잘 만들어지는지, 헤더 색과 총합계, 차트가 다 들어갔는지 확인해보세요.
  2. 응용 과제예요. summarize 함수에서 집계 키를 "지점" 대신 "날짜(월)"로 바꿔서, 월별 합계 버전도 만들어보세요. 힌트: for 날짜, 지점, 매출 in rows:에서 합계.get(지점, 0)합계.get(날짜, 0)으로 딱 한 군데만 바꾸면 돼요. 함수로 쪼갠 보람이 여기서 느껴지실 거예요.

여기서 한 발 더 나가면, 이 보고서를 매달 상사에게 자동으로 메일 첨부해서 보내거나, 사람이 실행 안 해도 매월 말일에 알아서 돌게 만들 수도 있어요. 그럼 진짜 손을 뗄 수 있겠죠?


엑셀 파트, 여기서 졸업이에요

읽고, 합치고, 집계하고, 서식·차트까지 넣은 보고서를 파이썬으로 만들 수 있게 됐어요. 05부터 여기까지, 엑셀 자동화 한 바퀴를 다 돈 거예요. 조각들이 이렇게 하나로 꿰어지는 게 느껴지시죠?

그런데 회사 문서가 엑셀만 있는 건 아니잖아요. 계약서, 명세서, 스캔본... 죄다 PDF죠. 다음 글부터는 이 골치 아픈 PDF를 파이썬으로 합치고 나누는 법으로 넘어갈 거예요.


 


제 글이 도움이 되셨다면 댓글 & 공감 부탁드려요 😀

 

 
728x90