파이썬 업무 자동화#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 |
이걸 파이썬으로 읽어올게요.
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] : 제목만 (차트·표를 직접 올릴 때 제일 편해요)
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단계 - 요약·차트·표 슬라이드 만들기
요약 슬라이드 (글머리 기호)
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 차트!)
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 : 범례가 그래프 영역을 파고들어 좁아지는 걸 막아줘요. 이거 안 넣으면 차트가 이상하게 찌그러집니다.
표 슬라이드
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-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초가 됩니다.
📌 보너스 - 회사 양식 그대로 쓰기 💡
디자인까지 맞추려면 회사 템플릿을 그대로 열어서 쓰면 돼요.
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로 변환해서 배포하는 방법을 다루고, 그다음엔 지금까지 배운 걸 전부 합친 🏆 최종 프로젝트 - 매일 아침 알아서 도착하는 업무 리포트 시스템으로 시리즈를 마무리할게요. 기대해주세요 🙌
궁금한 점은 댓글로 남겨주세요 💬 다음 편에서 만나요!
📚 파이썬 업무 자동화 시리즈
연재 · 14편 중 14번째
파이썬 업무 자동화
- #1엑셀 자동화 입문 (openpyxl)!
- #2웹 크롤링 입문 (requests + BeautifulSoup)!
- #3파일 정리 자동화 (이름 일괄 변경)!
- #4디스코드 웹훅(Webhook)으로 알림 보내기 (requests)!
- #5로컬 LLM(LM Studio)으로 고객 문의 자동 분류·요약하기! (무료 AI)
- #6예약 실행 자동화! 매일 정해진 시간에 스크립트 돌리기 (schedule·APScheduler)
- #7동적 페이지·로그인 자동화! requests로 안 되는 사이트 긁기 (Selenium)
- #8PDF 자동 처리! 여러 파일 병합·분할부터 비밀번호까지 (pypdf) 📄
- #9마우스·키보드 GUI 자동화! 클릭·타이핑을 파이썬이 대신하게 하기 (PyAutoGUI)
- #10워드(Word) 문서 자동 생성! 엑셀 명단으로 계약서·안내문 100장 뽑기 (python-docx) 📄
- #11개인화 대량 메일 자동 발송! 명단별 첨부파일·발송 로그·재시도까지 (smtplib 실전편) 📬
- #12구글 시트 연동하기! 팀원과 같이 보는 자동 시트 (gspread) 📗
- #13파이썬 자동화 로그·재시도·디스코드 실패 알림 만들기 - 조용히 멈추는 스크립트 잡기
- #14파워포인트(PowerPoint) 보고서 자동 생성! 엑셀 숫자만 바꾸면 발표자료 완성 (python-pptx) 📊지금 읽는 글
