目录
- 前言
- 基本使用
- 图片和图形
- 生成流程图
- 生成图表
- 生成数据表格
- 导出素材和数据
- 导出图片和幻灯片备注
- 导出表格和及图表中的数据
- 总结
前言
和Word、Excel承载数据的能力相比,PPT的应用重点在于表演。
比如一场发布会、一场演说、一次产品展示、一次客户沟通……
正常情况下,用PowerPoint等软件制作PPT是明智的选择,简单实用。
但如果需要批量或者自动化处理PPT文件时,用Python就能节省很多时间。
比如:
- 用PPT批量生成一些发布会风格的图片
- 从大量PPT中提取表格数据
- 用PPT批量制作简单动画
处理PPT的Python库,主要有两类:
- pywin32,调用操作系统上已装好的PPT软件。
- python-pptx,根据OpenXML规范解析XML文件。
日常处理推荐使用python-pptx模块,它基于PIL处理图片,基于xlsxwriter处理图表。
但如果需要批量转换PPT文件格式,如导出成PDF,那就得用pywin32,而且只能在Windows操作系统上操作。
本文主要介绍python-pptx用法,模块安装:pip install python-pptx。
注意:python-pptx暂时还不支持SmartArt和动画。如果需要批量生成,就得自己操作XML文件中对应内容。
基本使用
PPT中基本元素包括:
- slide(幻灯片):一个PPT由一系列slide构成。
- slide_master(幻灯片母版):母版可定义主题样式基准。
- slide_layouts(模版):创建幻灯片时可选择的模版。
- shape(形状):包含一切可视元素,通过slide.shapes可访问slide内元素。
- placeholder(占位符):在模板中占据位置,如图片、文字等。
- paragraph(段落):文本段,可以直接设置整段文本样式。
- text(文本):段落内的文本,通过paragraph.add_run()生成。
import pathlib | |
from pptx import Presentation | |
from pptx.enum.dml import MSO_THEME_COLOR | |
from pptx.util import Inches, Pt | |
path = list(pathlib.Path.cwd().parents)[].joinpath('data/automate/005ppt') | |
out_path = path.joinpath('ppt_basic.pptx') | |
prs = Presentation() | |
# 查看所有布局 | |
for lo in prs.slide_layouts: | |
print(lo.name) | |
# 创建标题页 | |
title_slide_layout = prs.slide_layouts[] | |
print(w, h, lo, ma) | |
slide = prs.slides.add_slide(title_slide_layout) | |
title = slide.shapes.title | |
subtitle = slide.placeholders[] | |
title.text = '只差一个程序员了' | |
subtitle.text = '程一初' | |
# 设置背景色 | |
fill = slide.shapes[].fill | |
fill.solid() | |
fill.fore_color.theme_color = MSO_THEME_COLOR.ACCENT_ | |
fill.fore_color.brightness = -.25 | |
# 创建第一页内容 | |
bullet_slide_layout = prs.slide_layouts[] | |
slide = prs.slides.add_slide(bullet_slide_layout) | |
shapes = slide.shapes | |
title_shape = shapes.title | |
body_shape = shapes.placeholders[] | |
title_shape.text = 'Python' | |
tf = body_shape.text_frame | |
tf.text = '第一级内容' | |
p = tf.add_paragraph() | |
p.text = '第一级段落内容' | |
p.level = | |
p = tf.add_paragraph() | |
p.text = '第二级段落内容' | |
p.level = | |
run = p.add_run() | |
# 与`python-docx`里的用法类似,但内容设置在`text`中 | |
run.text = '自定义字体' | |
run.font.name = '微软雅黑' | |
run.font.size = Pt() | |
run.font.bold = True | |
print(tf.paragraphs[].text) | |
# 从一个空白页开始创建内容 | |
blank_slide_layout = prs.slide_layouts[] | |
slide = prs.slides.add_slide(blank_slide_layout) | |
left = top = width = height = Inches() | |
txt_box = slide.shapes.add_textbox(left, top, width, height) | |
tf = txt_box.text_frame | |
tf.text = '文本框内容' | |
p = tf.add_paragraph() | |
p.text = '文本框内增加一个粗体段落' | |
p.font.bold = True | |
p = tf.add_paragraph() | |
p.text = '文本框内增加一个大字体段落' | |
p.font.size = Pt() | |
prs.save(out_path) |
图片和图形
PPT中图相关元素有3类:
- picture:图像,单位是Inch
- autoshape:形状,如流程图
- chart:图表,如直方图、饼图
生成流程图
import pathlib | |
from pptx import Presentation | |
from pptx.util import Inches, Pt | |
from pptx.enum.shapes import MSO_SHAPE | |
path = list(pathlib.Path.cwd().parents)[].joinpath('data/automate/005ppt') | |
out_path = path.joinpath('ppt_image.pptx') | |
img_path = path.joinpath('image.jpg') | |
prs = Presentation() | |
blank_slide_layout = prs.slide_layouts[] | |
slide = prs.slides.add_slide(blank_slide_layout) | |
# 增加两张图片,一大一小 | |
left = top = Inches() | |
pic = slide.shapes.add_picture(str(img_path), left, top) | |
left = Inches() | |
height = Inches() | |
pic = slide.shapes.add_picture(str(img_path), left, top, height=height) | |
# 增加一个AutoShape流程图 | |
title_only_slide_layout = prs.slide_layouts[] | |
slide = prs.slides.add_slide(title_only_slide_layout) | |
shapes = slide.shapes | |
shapes.title.text = '流程图' | |
left = Inches(.93) # 0.93" centers this overall set of shapes | |
top = Inches(.0) | |
width = Inches(.75) | |
height = Inches(.0) | |
shape = shapes.add_shape(MSO_SHAPE.PENTAGON, left, top, width, height) | |
shape.text = '第一步' | |
left = left + width - Inches(.4) | |
# 第一个形状左边平,第二个开始,左边有尖角,需要更长 | |
width = Inches(.0) | |
for n in range(, 6): | |
shape = shapes.add_shape(MSO_SHAPE.CHEVRON, left, top, width, height) | |
shape.text = f'第{n}步' | |
left = left + width - Inches(.4) | |
prs.save(out_path) |
生成图表
import pathlib | |
from pptx import Presentation | |
from pptx.util import Inches, Pt | |
from pptx.chart.data import CategoryChartData | |
from pptx.enum.chart import XL_CHART_TYPE, XL_TICK_MARK, XL_LABEL_POSITION, XL_LEGEND_POSITION | |
from pptx.dml.color import RGBColor | |
path = list(pathlib.Path.cwd().parents)[].joinpath('data/automate/005ppt') | |
out_path = path.joinpath('ppt_chart.pptx') | |
prs = Presentation() | |
blank_slide_layout = prs.slide_layouts[] | |
slide = prs.slides.add_slide(blank_slide_layout) | |
# 增加一个图表 | |
chart_data = CategoryChartData() | |
chart_data.categories = ['A销售额', 'B销售额', 'C销售额'] | |
chart_data.add_series('Q销售', (100, 120, 200)) | |
chart_data.add_series('Q销售', (120, 150, 180)) | |
chart_data.add_series('Q销售', (150, 180, 120)) | |
chart_data.add_series('Q销售', (130, 210, 150)) | |
x, y, cx, cy = Inches(), Inches(2), Inches(6), Inches(4) | |
chart = slide.shapes.add_chart( | |
XL_CHART_TYPE.COLUMN_CLUSTERED, x, y, cx, cy, chart_data | |
).chart | |
# 设置图表的轴信息 | |
category_axis = chart.category_axis | |
category_axis.has_major_gridlines = True | |
category_axis.minor_tick_mark = XL_TICK_MARK.OUTSIDE | |
category_axis.tick_labels.font.italic = True | |
category_axis.tick_labels.font.size = Pt() | |
plot = chart.plots[] # 用Plot对象访问标签 | |
plot.has_data_labels = True | |
data_labels = plot.data_labels | |
data_labels.font.size = Pt() | |
data_labels.font.color.rgb = RGBColor(x0A, 0x42, 0x80) | |
data_labels.position = XL_LABEL_POSITION.INSIDE_END | |
chart.has_legend = True | |
chart.legend.position = XL_LEGEND_POSITION.RIGHT | |
chart.legend.include_in_layout = False | |
# 增加一个饼图 | |
slide = prs.slides.add_slide(blank_slide_layout) | |
chart_data = CategoryChartData() | |
chart_data.categories = ['A销售额', 'B销售额', 'C销售额'] | |
chart_data.add_series('年度销售比', (.276, 0.365, 0.359)) | |
chart = slide.shapes.add_chart( | |
XL_CHART_TYPE.PIE, x, y, cx, cy, chart_data | |
).chart | |
chart.has_legend = True | |
chart.legend.position = XL_LEGEND_POSITION.BOTTOM | |
chart.legend.include_in_layout = False | |
chart.plots[].has_data_labels = True | |
data_labels = chart.plots[].data_labels | |
data_labels.number_format = '%' | |
data_labels.position = XL_LABEL_POSITION.OUTSIDE_END | |
prs.save(out_path) |
注意,关于图形类的处理,python-pptx模块暂不支持下面两种场景:
- SmartArt,微软并没有公开发布过其标准格式,目前只有PowerPoint软件具备这个功能。
- PPT文件按页导出成图片,这也是PowerPoint软件功能,在Windows上可以借助win32com等模块调用PPT软件功能实现。
生成数据表格
PPT中也可以插入表格,只不过表格功能非常有限,仅用于展示。
在自动生成PPT时,有时候需要把外部数据复制到PPT内,可以读取数据后在PPT中插入表格。
比如:
import pathlib | |
from pptx import Presentation | |
from pptx.util import Inches | |
path = list(pathlib.Path.cwd().parents)[].joinpath('data/automate/005ppt') | |
out_path = path.joinpath('ppt_table.pptx') | |
prs = Presentation() | |
title_only_slide_layout = prs.slide_layouts[] | |
slide = prs.slides.add_slide(title_only_slide_layout) | |
shapes = slide.shapes | |
shapes.title.text = '表格数据' | |
rows, cols =, 4 | |
left = top = Inches(.0) | |
width = Inches(.0) | |
height = Inches(.8) | |
table = shapes.add_table(rows, cols, left, top, width, height).table | |
data = [ | |
('季度', 'A销售', 'B销售', 'C销售'), # 表头 | |
('Q', 100, 120, 200), | |
('Q', 120, 150, 180), | |
('Q', 150, 180, 120), | |
('Q', 130, 210, 150), | |
] | |
for col in table.columns: | |
col.width = Inches(.6) | |
table.columns[].width = Inches(1.0) | |
for i, d in enumerate(data): | |
for j in range(): | |
table.cell(i, j).text = str(data[i][j]) | |
prs.save(out_path) |
导出素材和数据
导出图片和幻灯片备注
之前说过,想提取docx、xlsx、pptx文件里的图片素材,可以先把文件解压缩,然后到对应目录查看图片。其中pptx文件解压后,图片在ppt/media文件夹内。
当然,也可以通过Python自动导出。
import pathlib | |
from pptx import Presentation | |
from pptx.util import Inches, Pt | |
from pptx.enum.shapes import MSO_SHAPE_TYPE | |
# 借用openpyxl的单位转换 | |
from openpyxl.utils.units import EMU_to_pixels as ep | |
path = list(pathlib.Path.cwd().parents)[].joinpath('data/automate/005ppt') | |
img_out_path = path.joinpath('ppt_out_images') | |
txt_out_path = path.joinpath('ppt_notes.txt') | |
in_path = path.joinpath('input_covid.pptx') | |
prs = Presentation(in_path) | |
# EMU单位, Pixel = 9525 EMU | |
w, h = prs.slide_width, prs.slide_height | |
print(f'总共 {len(prs.slides)} 页,页宽 {ep(w)} px,页高 {e2p(h)} px') | |
cnt_img, txt_list =, [] | |
for i, slide in enumerate(prs.slides): | |
# 提取notes | |
notes_slide = slide.notes_slide | |
text_frame = notes_slide.notes_text_frame | |
txt_list.append(f'第 {i} 页:') | |
txt_list.append(text_frame.text) | |
# 提取图片 | |
for s in slide.shapes: | |
if s.shape_type == MSO_SHAPE_TYPE.PICTURE: | |
img = s.image | |
cnt_img += | |
ext = img.content_type.split('/')[] | |
f_out_path = img_out_path.joinpath(f'{cnt_img}.{ext}') | |
with open(f_out_path, 'wb') as f: | |
f.write(img.blob) | |
print(f'总导出 {cnt_img} 张图片.') | |
with open(txt_out_path, 'w') as txt_fout: | |
txt_fout.write('\n'.join(txt_list)) |
导出表格和及图表中的数据
从上面导出图片的过程可以看到,想要导出一个元素,关键是识别出它的类型。
所以,导出表格和图表的关键在于:识别出table和chart。
另外,有2个注意点:
- table的数据可以直接获取,但注意python-pptx中表格索引从0开始(openpyxl从1开始)
- chart的数据在其内部,需要通过plots.categories提取
import pathlib | |
from pptx import Presentation | |
from pptx.chart.data import CategoryChartData | |
from pptx.util import Inches, Pt | |
from pptx.enum.shapes import MSO_SHAPE_TYPE | |
from openpyxl.utils.units import EMU_to_pixels as ep | |
from openpyxl import Workbook | |
path = list(pathlib.Path.cwd().parents)[].joinpath('data/automate/005ppt') | |
table_out_path = path.joinpath('ppt_out_tables.xlsx') | |
chart_data_out_path = path.joinpath('ppt_out_chart_data.xlsx') | |
chart_out_path = path.joinpath('ppt_out_charts') | |
in_path = path.joinpath('input_table.pptx') | |
prs = Presentation(in_path) | |
# EMU单位, Pixel = 9525 EMU | |
w, h = prs.slide_width, prs.slide_height | |
print(f'总共 {len(prs.slides)} 页,页宽 {ep(w)} px,页高 {e2p(h)} px') | |
cnt_tables, cnt_charts =, 0 | |
xlsx_wb = Workbook() | |
chart_wb = Workbook() | |
for i, slide in enumerate(prs.slides, start=): | |
for j, s in enumerate(slide.shapes, start=): | |
if s.shape_type == MSO_SHAPE_TYPE.TABLE and s.has_table: | |
cnt_tables += | |
tbl = s.table | |
cnt_row, cnt_col = len(tbl.rows), len(tbl.columns) | |
print(f'增加表格 {cnt_tables}: {cnt_row} x {cnt_col}') | |
ws = xlsx_wb.create_sheet(f'{cnt_tables}_page_{i}') | |
for r in range(cnt_row): | |
for c in range(cnt_col): | |
cell = tbl.cell(r,c) | |
text_runs = [] | |
for paragraph in cell.text_frame.paragraphs: | |
for run in paragraph.runs: | |
text_runs.append(run.text) | |
ws.cell(row=r+, column=c+1, value=''.join(text_runs)) | |
elif s.shape_type == MSO_SHAPE_TYPE.CHART: | |
chart = s.chart | |
cnt_charts += | |
# 导出Chart的数据到表格 | |
data = chart.series | |
# for plot in chart.plots: | |
# cat = list(plot.categories) | |
cat = list(chart.plots[].categories) # 假设1个图表 | |
if len(data) >: | |
ws = chart_wb.create_sheet(f'{cnt_charts}_page_{i}') | |
print(f'增加Chart数据 {cnt_charts}: {len(data)}行, {len(cat)} 列') | |
cat = ['标签'] + cat | |
ws.append(cat) | |
for d in data: | |
values = [d.name] + list(d.values) | |
ws.append(values) | |
xlsx_wb.save(table_out_path) | |
chart_wb.save(chart_data_out_path) |
总结
本文重点介绍了python-pptx的使用方法,包括基本文件读写、图形图像处理,以及表格图表内部数据的抽取等。
虽然PPT能装载的数据有限,但它的表达能力丰富,关键是看怎样应用。不同的配色、布局、形状、内容,会产生完全不同的效果。
最后,可以尝试下用PPT制作海报卡片,在PPT中批量生成slide是件容易的事,再打开软件导出图片即可。比如把豆瓣上的高分电影,随机配上金句文案:
顺便再生成一个电影评分榜。