카테고리: 업무 자동화

파이썬 업무 자동화#14파워포인트(PowerPoint) 보고서 자동 생성! 엑셀 숫자만 바꾸면 발표자료 완성 (python-pptx) 📊

목차13개 항목

안녕하세요! Mojangcam입니다 😎

지난 편에서는 엑셀 데이터를 구글 시트에 자동으로 올리는 방법(gspread)을 다뤘어요. 데이터를 모으고, 저장하고, 공유하는 것까지는 이제 파이썬이 다 해주죠.

그런데 회사 일은 거기서 안 끝나요. 결국 누군가는 그 숫자를 PPT로 옮겨야 합니다. 매달 똑같은 양식에, 숫자만 바뀌는 보고서를요. 저도 예전에 월말마다 표 붙이고 그래프 다시 그리느라 두 시간씩 날렸어요.

오늘은 그 작업을 파이썬이 대신 만들게 해볼게요. 엑셀 파일 하나만 바꾸면 제목 슬라이드 → 요약 → 차트 → 상세 표까지 4장짜리 보고서가 자동으로 튀어나옵니다. 라이브러리는 python-pptx 하나면 돼요!

📌 준비물

  • Python 3.8 이상 (python-pptx 1.0.2의 Requires-Python 조건이 >=3.8이에요)
  • python-pptx 1.0.2 — PPTX 파일을 만들고 편집하는 라이브러리
  • openpyxl 3.1.x — 원본 데이터인 엑셀을 읽을 때 사용
  • 파워포인트는 설치 안 돼 있어도 됩니다! python-pptx는 파일 포맷을 직접 다루기 때문에 오피스가 없어도 .pptx가 만들어져요. (한컴·구글 슬라이드·리브레오피스에서도 열립니다)
터미널
pip install python-pptx openpyxl

설치 확인은 이렇게 해요.

터미널
python -c "import pptx; print(pptx.__version__)"
# 1.0.2

💡 패키지 이름은 python-pptx인데 import 할 때는 pptx 예요. pip install pptx를 치면 전혀 다른 패키지가 깔리니 주의하세요!

📌 1단계 - 원본 엑셀 준비하고 읽어오기

먼저 보고서의 재료가 될 엑셀을 하나 만들어요. sales.xlsx 첫 시트에 이렇게만 있으면 됩니다.

팀 목표 실적
영업1팀 5000 5400
영업2팀 4500 3900
온라인팀 3000 3600

이걸 파이썬으로 읽어올게요.

Python
import openpyxl

def load_rows(xlsx_path):
    wb = openpyxl.load_workbook(xlsx_path, data_only=True)
    ws = wb.active
    rows = []
    for name, target, actual in ws.iter_rows(min_row=2, values_only=True):
        if name is None:
            continue
        rows.append({"name": str(name), "target": int(target), "actual": int(actual)})
    return rows

하나씩 뜯어볼게요 💡

  • data_only=True : 셀에 수식이 들어 있어도 계산된 결과값을 가져와요. 이걸 빼면 =B2*1.1 같은 수식 문자열이 그대로 들어옵니다.
  • ws.iter_rows(min_row=2) : 1행은 머리글이니 2행부터 읽어요.
  • values_only=True : 셀 객체 대신 값만 튜플로 주기 때문에 바로 언패킹해서 쓸 수 있어요.
  • if name is None: continue : 엑셀 아래쪽 빈 줄이 딸려 들어오는 걸 걸러줍니다. 이거 안 하면 int(None) 에러 나요!

📌 2단계 - 슬라이드 레이아웃 이해하고 제목 슬라이드 만들기

python-pptx에서 제일 헷갈리는 게 레이아웃 번호예요. 기본 템플릿에는 11개 레이아웃이 들어 있는데, 실무에서 쓰는 건 사실상 세 개입니다.

  • slide_layouts[0] : 제목 슬라이드 (제목 + 부제)
  • slide_layouts[1] : 제목 + 내용 (글머리 기호 본문)
  • slide_layouts[5] : 제목만 (차트·표를 직접 올릴 때 제일 편해요)
Python
from pptx import Presentation
from pptx.util import Inches

prs = Presentation()
prs.slide_width = Inches(10)      # 4:3 비율
prs.slide_height = Inches(7.5)

slide = prs.slides.add_slide(prs.slide_layouts[0])
slide.shapes.title.text = "2026년 9월 실적 보고"
slide.placeholders[1].text = "자동 생성 · Mojangcam"

하나씩 뜯어볼게요 💡

  • Presentation() : 인자 없이 부르면 기본 템플릿으로 시작해요. 회사 양식이 있다면 Presentation("회사양식.pptx")처럼 넣으면 그 디자인을 그대로 씁니다. 이게 진짜 실무 꿀팁이에요!
  • slide_width / slide_height : 기본은 4:3(10×7.5인치)이에요. 16:9로 하고 싶으면 Inches(13.333) × Inches(7.5) 로 바꿔주세요.
  • shapes.title : 레이아웃에 들어 있는 제목 자리(플레이스홀더)를 가져옵니다.
  • placeholders[1] : 0번이 제목, 1번이 부제(또는 본문)예요. 번호는 레이아웃마다 다르니, 헷갈리면 for ph in slide.placeholders: print(ph.placeholder_format.idx, ph.name) 으로 찍어보면 됩니다.

📌 3단계 - 요약·차트·표 슬라이드 만들기

요약 슬라이드 (글머리 기호)

Python
from pptx.dml.color import RGBColor

def add_summary_slide(prs, rows):
    slide = prs.slides.add_slide(prs.slide_layouts[1])
    slide.shapes.title.text = "요약"
    body = slide.placeholders[1].text_frame

    total_t = sum(r["target"] for r in rows)
    total_a = sum(r["actual"] for r in rows)
    body.text = f"전체 목표 {total_t:,}만원 / 실적 {total_a:,}만원"

    for r in rows:
        rate = r["actual"] / r["target"] * 100
        p = body.add_paragraph()
        p.text = f'{r["name"]} : 달성률 {rate:.1f}%'
        p.level = 1
        if rate < 100:
            p.runs[0].font.color.rgb = RGBColor(0xC0, 0x00, 0x00)
  • text_frame.text = ... : 첫 번째 문단을 채우는 코드예요. 두 번째 줄부터는 add_paragraph()를 써야 합니다.
  • p.level = 1 : 들여쓰기 단계예요. 0이 최상위, 숫자가 커질수록 안으로 들어가요.
  • p.runs[0].font.color.rgb : 목표 미달인 팀만 빨간 글씨로 강조했어요. p.text로 값을 넣으면 런(run)이 딱 하나 생기니 runs[0]으로 잡으면 됩니다.

차트 슬라이드 (진짜 편집 가능한 PPT 차트!)

Python
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE, XL_LEGEND_POSITION

def add_chart_slide(prs, rows):
    slide = prs.slides.add_slide(prs.slide_layouts[5])
    slide.shapes.title.text = "팀별 목표 대비 실적"

    data = CategoryChartData()
    data.categories = [r["name"] for r in rows]
    data.add_series("목표", [r["target"] for r in rows])
    data.add_series("실적", [r["actual"] for r in rows])

    graphic_frame = slide.shapes.add_chart(
        XL_CHART_TYPE.COLUMN_CLUSTERED,
        Inches(0.8), Inches(1.8), Inches(8.4), Inches(4.5),
        data,
    )
    chart = graphic_frame.chart
    chart.has_legend = True
    chart.legend.position = XL_LEGEND_POSITION.BOTTOM
    chart.legend.include_in_layout = False

여기가 python-pptx의 최고 장점이에요. 이미지가 아니라 파워포인트 네이티브 차트가 들어갑니다. 발표 직전에 팀장님이 "이 숫자 좀 고쳐줘" 하면 PPT에서 바로 편집돼요! 🙌

  • XL_CHART_TYPE.COLUMN_CLUSTERED : 묶은 세로 막대. 꺾은선은 LINE, 원그래프는 PIE, 가로 막대는 BAR_CLUSTERED예요.
  • add_chart(종류, left, top, width, height, data) : 위치·크기는 전부 Inches()나 Cm()로 넣어요. 슬라이드 왼쪽 위가 (0, 0) 기준입니다.
  • legend.include_in_layout = False : 범례가 그래프 영역을 파고들어 좁아지는 걸 막아줘요. 이거 안 넣으면 차트가 이상하게 찌그러집니다.

표 슬라이드

Python
from pptx.util import Pt

def add_table_slide(prs, rows):
    slide = prs.slides.add_slide(prs.slide_layouts[5])
    slide.shapes.title.text = "상세 표"

    table = slide.shapes.add_table(
        len(rows) + 1, 4, Inches(0.8), Inches(1.8), Inches(8.4), Inches(0.8)
    ).table

    for col, head in enumerate(["팀", "목표", "실적", "달성률"]):
        table.cell(0, col).text = head

    for i, r in enumerate(rows, start=1):
        rate = r["actual"] / r["target"] * 100
        table.cell(i, 0).text = r["name"]
        table.cell(i, 1).text = f'{r["target"]:,}'
        table.cell(i, 2).text = f'{r["actual"]:,}'
        table.cell(i, 3).text = f"{rate:.1f}%"
  • add_table(행수, 열수, left, top, width, height) : 머리글 한 줄을 더해야 하니 len(rows) + 1 이에요.
  • 마지막에 .table 을 꼭 붙여야 합니다! add_table()이 돌려주는 건 도형(GraphicFrame)이고, 셀을 만지려면 그 안의 .table이 필요해요. 이거 빠뜨려서 AttributeError 보는 분 정말 많아요 😅
  • height 는 최소 높이라서, 행이 많아지면 파워포인트가 알아서 늘려줍니다.

📌 전체 코드 (복붙 OK)

Python
# python-pptx 1.0.2 / openpyxl 3.1.x / Python 3.8+
from pathlib import Path

import openpyxl
from pptx import Presentation
from pptx.chart.data import CategoryChartData
from pptx.dml.color import RGBColor
from pptx.enum.chart import XL_CHART_TYPE, XL_LEGEND_POSITION
from pptx.util import Inches, Pt

XLSX_PATH = Path("sales.xlsx")
OUT_PATH = Path("output/2026_09_report.pptx")
POINT_COLOR = RGBColor(0x2E, 0x5B, 0xFF)

def load_rows(xlsx_path):
    wb = openpyxl.load_workbook(xlsx_path, data_only=True)
    ws = wb.active
    rows = []
    for name, target, actual in ws.iter_rows(min_row=2, values_only=True):
        if name is None:
            continue
        rows.append({"name": str(name), "target": int(target), "actual": int(actual)})
    return rows

def add_title_slide(prs, title, subtitle):
    slide = prs.slides.add_slide(prs.slide_layouts[0])
    slide.shapes.title.text = title
    slide.placeholders[1].text = subtitle
    return slide

def add_summary_slide(prs, rows):
    slide = prs.slides.add_slide(prs.slide_layouts[1])
    slide.shapes.title.text = "요약"
    body = slide.placeholders[1].text_frame
    total_t = sum(r["target"] for r in rows)
    total_a = sum(r["actual"] for r in rows)
    body.text = f"전체 목표 {total_t:,}만원 / 실적 {total_a:,}만원"
    for r in rows:
        rate = r["actual"] / r["target"] * 100
        p = body.add_paragraph()
        p.text = f'{r["name"]} : 달성률 {rate:.1f}%'
        p.level = 1
        if rate < 100:
            p.runs[0].font.color.rgb = RGBColor(0xC0, 0x00, 0x00)
    return slide

def add_chart_slide(prs, rows):
    slide = prs.slides.add_slide(prs.slide_layouts[5])
    slide.shapes.title.text = "팀별 목표 대비 실적"

    data = CategoryChartData()
    data.categories = [r["name"] for r in rows]
    data.add_series("목표", [r["target"] for r in rows])
    data.add_series("실적", [r["actual"] for r in rows])

    graphic_frame = slide.shapes.add_chart(
        XL_CHART_TYPE.COLUMN_CLUSTERED,
        Inches(0.8), Inches(1.8), Inches(8.4), Inches(4.5),
        data,
    )
    chart = graphic_frame.chart
    chart.has_legend = True
    chart.legend.position = XL_LEGEND_POSITION.BOTTOM
    chart.legend.include_in_layout = False
    chart.plots[0].series[1].format.fill.solid()
    chart.plots[0].series[1].format.fill.fore_color.rgb = POINT_COLOR
    return slide

def add_table_slide(prs, rows):
    slide = prs.slides.add_slide(prs.slide_layouts[5])
    slide.shapes.title.text = "상세 표"

    table = slide.shapes.add_table(
        len(rows) + 1, 4, Inches(0.8), Inches(1.8), Inches(8.4), Inches(0.8)
    ).table
    for col, head in enumerate(["팀", "목표", "실적", "달성률"]):
        table.cell(0, col).text = head
    for i, r in enumerate(rows, start=1):
        rate = r["actual"] / r["target"] * 100
        table.cell(i, 0).text = r["name"]
        table.cell(i, 1).text = f'{r["target"]:,}'
        table.cell(i, 2).text = f'{r["actual"]:,}'
        table.cell(i, 3).text = f"{rate:.1f}%"
    for row in table.rows:
        for cell in row.cells:
            for p in cell.text_frame.paragraphs:
                for run in p.runs:
                    run.font.size = Pt(14)
    return slide

def main():
    rows = load_rows(XLSX_PATH)
    prs = Presentation()
    prs.slide_width = Inches(10)      # 16:9는 Inches(13.333)
    prs.slide_height = Inches(7.5)

    add_title_slide(prs, "2026년 9월 실적 보고", "자동 생성 · Mojangcam")
    add_summary_slide(prs, rows)
    add_chart_slide(prs, rows)
    add_table_slide(prs, rows)

    OUT_PATH.parent.mkdir(parents=True, exist_ok=True)
    prs.save(OUT_PATH)
    print(f"완료: {OUT_PATH} (슬라이드 {len(prs.slides)}장)")

if __name__ == "__main__":
    main()

📌 실행 결과

터미널
python make_report.py
터미널
완료: output/2026_09_report.pptx (슬라이드 4장)

output 폴더에 생긴 파일을 열어보면 제목 → 요약 → 차트 → 표 순서로 4장이 만들어져 있어요. 차트를 클릭해서 "데이터 편집"을 눌러보면 엑셀 창이 뜹니다. 진짜 PPT 차트라는 증거죠 😎

이제 다음 달엔요? sales.xlsx의 숫자만 바꾸고 다시 실행하면 끝이에요. 두 시간짜리 일이 2초가 됩니다.

📌 보너스 - 회사 양식 그대로 쓰기 💡

디자인까지 맞추려면 회사 템플릿을 그대로 열어서 쓰면 돼요.

Python
prs = Presentation("회사양식.pptx")

# 이 템플릿에 어떤 레이아웃이 있는지 먼저 확인!
for i, layout in enumerate(prs.slide_layouts):
    print(i, layout.name)

템플릿마다 레이아웃 순서가 다르기 때문에, 번호를 찍어보고 원하는 레이아웃 인덱스를 골라 쓰는 게 안전해요. 기존 슬라이드를 남기고 뒤에 이어 붙일 수도 있고, 빈 양식 파일(마스터만 있는 pptx)을 만들어두고 쓰는 방법도 좋아요.

📌 자주 나는 에러

1. ModuleNotFoundError: No module named 'pptx'

  • 원인: pip install pptx로 잘못 설치했거나, 다른 가상환경에 설치됨
  • 해결: pip install python-pptx 로 다시 설치하세요. 설치 위치가 의심되면 python -m pip install python-pptx처럼 실행할 파이썬과 같은 인터프리터로 설치하면 확실해요.

2. AttributeError: 'GraphicFrame' object has no attribute 'cell'

  • 원인: add_table() 뒤에 .table을 안 붙였어요
  • 해결: shapes.add_table(...).table 로 끝까지 붙여주세요.

3. KeyError: 'no placeholder on this slide with idx == 1'

  • 원인: 그 레이아웃에는 1번 플레이스홀더가 없어요. 특히 slide_layouts[5](제목만)에는 본문 자리가 없습니다
  • 해결: for ph in slide.placeholders: print(ph.placeholder_format.idx, ph.name) 으로 실제 번호를 확인하거나, 본문이 필요하면 slide_layouts[1]을 쓰세요.

4. PermissionError: [Errno 13] Permission denied: '...pptx'

  • 원인: 만들려는 PPT 파일을 파워포인트에서 열어둔 채로 실행했어요
  • 해결: 파일을 닫고 다시 실행하세요. 실무에선 아예 2026_09_report_20260920.pptx처럼 날짜를 파일명에 붙여 덮어쓰기를 피하는 게 좋아요.

5. 한글이 네모(□□□)로 보여요

  • 원인: 코드 문제가 아니라 발표용 PC에 해당 폰트가 없는 경우예요
  • 해결: run.font.name = "맑은 고딕" 처럼 어느 PC에나 있는 기본 폰트로 지정하세요. 특이한 폰트는 파일과 함께 안 따라갑니다.

📌 마무리

오늘은 python-pptx로 엑셀 데이터 → 4장짜리 PPT 보고서를 자동 생성해봤어요. 정리하면요 💬

  • Presentation() 으로 시작, slide_layouts[0/1/5] 세 개만 알면 대부분 해결
  • 차트는 CategoryChartData + add_chart() → 이미지가 아닌 진짜 PPT 차트
  • 표는 add_table(...).table 로 끝에 .table 잊지 않기
  • 회사 양식은 Presentation("양식.pptx") 로 그대로 재사용

여기에 지난 편들을 붙이면 완성형이 돼요. 크롤링·시트로 데이터를 모으고 → 오늘 배운 걸로 PPT를 만들고 → 메일 자동 발송으로 팀장님께 보내기까지, 사람 손이 아예 안 닿는 파이프라인이 됩니다!

다음 편에서는 만든 보고서를 PDF로 변환해서 배포하는 방법을 다루고, 그다음엔 지금까지 배운 걸 전부 합친 🏆 최종 프로젝트 - 매일 아침 알아서 도착하는 업무 리포트 시스템으로 시리즈를 마무리할게요. 기대해주세요 🙌

궁금한 점은 댓글로 남겨주세요 💬 다음 편에서 만나요!

📚 파이썬 업무 자동화 시리즈

  • #10 - 워드(Word) 문서 자동 생성 (python-docx) → 보러가기
  • #11 - 개인화 대량 메일 자동 발송 (smtplib) → 보러가기
  • #12 - 구글 시트 연동하기 (gspread) → 보러가기
  • #13 - 로그·재시도·실패 알림 체계 만들기 (logging·tenacity)
  • #14 - 파워포인트 보고서 자동 생성 (python-pptx) ← 지금 이 글!
  • #15 - PDF 변환·배포 (예정)

연재 · 14편 중 14번째

파이썬 업무 자동화

  1. #1엑셀 자동화 입문 (openpyxl)!
  2. #2웹 크롤링 입문 (requests + BeautifulSoup)!
  3. #3파일 정리 자동화 (이름 일괄 변경)!
  4. #4디스코드 웹훅(Webhook)으로 알림 보내기 (requests)!
  5. #5로컬 LLM(LM Studio)으로 고객 문의 자동 분류·요약하기! (무료 AI)
  6. #6예약 실행 자동화! 매일 정해진 시간에 스크립트 돌리기 (schedule·APScheduler)
  7. #7동적 페이지·로그인 자동화! requests로 안 되는 사이트 긁기 (Selenium)
  8. #8PDF 자동 처리! 여러 파일 병합·분할부터 비밀번호까지 (pypdf) 📄
  9. #9마우스·키보드 GUI 자동화! 클릭·타이핑을 파이썬이 대신하게 하기 (PyAutoGUI)
  10. #10워드(Word) 문서 자동 생성! 엑셀 명단으로 계약서·안내문 100장 뽑기 (python-docx) 📄
  11. #11개인화 대량 메일 자동 발송! 명단별 첨부파일·발송 로그·재시도까지 (smtplib 실전편) 📬
  12. #12구글 시트 연동하기! 팀원과 같이 보는 자동 시트 (gspread) 📗
  13. #13파이썬 자동화 로그·재시도·디스코드 실패 알림 만들기 - 조용히 멈추는 스크립트 잡기
  14. #14파워포인트(PowerPoint) 보고서 자동 생성! 엑셀 숫자만 바꾸면 발표자료 완성 (python-pptx) 📊지금 읽는 글
전체 14편 보기접기

글쓴이

Mojangcam

직접 코드를 짜고, 서버를 돌리고, 막힌 에러를 해결하며 배운 것들을 기록하는 개발·IT 실전 기록 블로그