分类: 办公自动化
Python 办公自动化#14自动生成 PowerPoint 报告!只改 Excel 里的数字,演示文稿就做好了 (python-pptx) 📊
本文译自韩语原文。
目录13 项
大家好!我是 Mojangcam 😎
上一篇我们讲了如何把 Excel 数据自动上传到 Google 表格(gspread)。收集、保存、共享数据,现在都可以交给 Python 了。
但公司的工作并不会到此为止。最终总得有人把这些数字搬进 PPT。 每个月都是同样的模板,只有数字在变的报告。我以前每到月底都要贴表格、重画图表,一弄就是两个小时。
今天我们就让 Python 替你把它做出来。只要换掉一个 Excel 文件,就会自动生成标题页 → 摘要 → 图表 → 详细表格共 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 — 用来读取作为原始数据的 Excel
- PowerPoint 没有安装也没关系! python-pptx 直接处理文件格式,所以没有 Office 也能生成 .pptx。(用 Hancom Office、Google 幻灯片、LibreOffice 也都能打开)
pip install python-pptx openpyxl可以这样确认是否安装成功。
python -c "import pptx; print(pptx.__version__)"
# 1.0.2💡 包名是 python-pptx,但 import 的时候是 pptx。如果输入 pip install pptx,装上的会是一个完全不同的包,一定要注意!
📌 第 1 步 - 准备并读取原始 Excel
首先创建一个作为报告素材的 Excel。sales.xlsx 的第一个工作表里只要有这些内容就行。
| 团队 | 目标 | 业绩 |
|---|---|---|
| 销售一组 | 5000 | 5400 |
| 销售二组 | 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 : 过滤掉 Excel 底部被一起读进来的空行。不加这一句就会报
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 最大的优点。插入的不是图片,而是 PowerPoint 原生图表。演示前领导说一句"帮我改一下这个数字",在 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 是最小高度,行数变多时 PowerPoint 会自动把它撑高。
📌 完整代码 (直接复制可用)
# 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 页幻灯片。点一下图表,再点"编辑数据",就会弹出 Excel 窗口。这就是真正 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这样用运行脚本的同一个 Python 解释器来安装最保险。
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 文件还在 PowerPoint 里打开着,就运行了脚本
- 解决:关掉文件再运行。实际工作中最好像
2026_09_report_20260920.pptx这样在文件名里加上日期,避免覆盖。
5. 韩文等文字显示成方块(□□□)
- 原因:不是代码的问题,而是用来演示的电脑上没有对应的字体
- 解决:像
run.font.name = "맑은 고딕"(Malgun Gothic) 这样,指定任何电脑上都有的默认字体。特殊字体不会跟着文件一起走。
📌 总结
今天我们用 python-pptx 自动生成了 Excel 数据 → 4 页 PPT 报告。总结一下 💬
- 从
Presentation()开始,只要掌握slide_layouts[0/1/5]这三个,大部分情况都能搞定 - 图表用
CategoryChartData+add_chart()→ 不是图片,而是真正的 PPT 图表 - 表格用
add_table(...).table,别忘了末尾的.table - 公司模板用
Presentation("양식.pptx")直接复用
再接上前几篇,就是一套完整的流程了。用爬虫和表格收集数据 → 用今天学的内容生成 PPT → 用邮件自动发送发给领导,就变成了一条完全不用人手参与的流水线!
下一篇会讲把生成的报告转换成 PDF 并分发的方法,再下一篇会把目前学到的全部组合起来,用 🏆 最终项目 - 每天早上自动送达的工作报告系统为这个系列收尾。敬请期待 🙌
有问题欢迎在评论区留言 💬 下一篇见!
📚 Python 办公自动化系列
连载 · 共 14 篇中的第 14 篇
Python 办公自动化
- #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 报告!只改 Excel 里的数字,演示文稿就做好了 (python-pptx) 📊当前文章
