日常工作中会遇见很多重复性的操作,利用Python可以让我们从繁忙的工作中解脱出来。本次要分析的就是运用Python操作PPT以实现自动化生产PPT文件。
工作中一同事需要结合Excel数据来制作PPT报告,以便甲方更好的看到商品部分指标的变化趋势。目前同事已经做好了一个模板文件,需要完成将它运用到其他数据上。
PPT样式如下:
原需求是想运用Python来生成PPT文件,于是需要了解到Python操作PPT的库,根据这个库来生成想要的PPT文件。
由于第一次接触到Python操作PPT,在网上得知,Python操作PPT的库为python-pptx。于是安装python-pptx.
pip install python-pptx
安装完之后就可以对pptx进行操作了。
由于第一次使用有些操作不是很熟,阅读了网上的一些案例,结合官方文档 便开始一顿操作了。
可当正要操作是便发现,网上的大多数教程都是如何创建一页简单的PPT,讲到了Presentation,占位符等对象,可如上图所示,我操作的是生成一个图表,还有一个表格,当PPT播放时,折线图还有一个动态的过程。于是我陷入了沉思,这个轮子我可造不了,对于python-pptx我还不是很了解,这可不能快速的完成自动化的任务。
虽然我不能创建PPT,但是目前已经有一个做好的模板PPT,借助于前段时间做过的生成报告证书的经历,报告证书是其中大多数数据不变,变化商品名称和日期然后输出pdf文件,在模板文件中运用占位符就可以解决。于是顺着这个思路,我能否通过读取PPT模板,修改其中的部分数据,而不改变结构,那么问题也就解决了。
于是运用python-pptx读取pptx文件,获取该对象,对该对象进行操作。
此时不得不打开官方文档了解其属性方法,一遍对其更好的操作。
慢,这样一来不又是进入了造轮子的阶段吗,我并不需要对pptx库进行全面的了解,核心是对数据进行替换。于是我只需要知道原PPT中一个数据对应的属性方法是什么就可以了(有点像爬虫),通过值找到对应的标签。下面正式开始。
读取pptx文件,读取一些基本知道该对象的属性方法
from pptx import Presentation
prs = Presentation("123.pptx")
slides = prs.slides
number_pages = len(slides)
page = slides[0]
print(dir(page.shapes))
有上面输出可以得到 每个PPT页面的对象都为一个slider,每页中出现的内容都在page.shape中。
可以发现page.shapes有如下属性:
‘adjustments’, ‘auto_shape_type’, ‘click_action’, ‘element’, ‘fill’, ‘get_or_add_ln’, ‘has_chart’, ‘has_table’, ‘has_text_frame’, ‘height’, ‘is_placeholder’, ‘left’, ‘line’, ‘ln’, ‘name’, ‘part’, ‘placeholder_format’, ‘rotation’, ‘shadow’, ‘shape_id’, ‘shape_type’, ‘text’, ‘text_frame’, ‘top’, ‘width’。
结合对pptx的初步了解,
text是和文字相关的,table是和表格相关的,chart是和图表相关的,其他的有一些边框大小,填充字体等属性。于是接着尝试
for shape in page.shapes:
# print(dir(shape))
if shape.has_chart:
print("ok")
if shape.has_tacle:
print("ok")
果然,返回的结果都是ok,也就是刚好和原来PPT中的折线图以及表格相对应。(感觉发现了新世纪的大门)
接着就是分别对chart和table进行操作了。(由于走了很多坑这里的顺序和实际完成的顺序不太一样)
table做为一个表格对象,从原图就可以看出,table有如下几种属性:变宽间距,字体颜色,字体形式,字体大小,对齐方式。刚好这里在table对象都有了很好的体现。而我要做的就是如何修改这些达到要求。这其中对于字体颜色,有些原pptx的颜色在python-pptx中并读不出来,这里的小技巧就是通过取色器获取原ppt文件中字体的RGB颜色,然后用python创建这样的一个颜色对象,来填充之前的属性。
for shape in page.shapes:
if shape.has_table:
table = shape.table
# 获取table的一个格,类似openpyxl
ii = 2
j = 1
cell = table.cell(ii, j + 1)
# 填充的数据
da = get_da(ci, j + 1)
pr = cell.text_frame.paragraphs[0].runs[0]
pr.text = da
# 字体大小
pr.font.size = Pt(11)
if da == "-" or da == "0%":
# 颜色
pr.font.color.rgb = RGBColor(0, 0, 0)
elif "-" in da:
pr.font.color.rgb = RGBColor(156, 0, 6)
else:
pr.font.color.rgb = RGBColor(84, 130, 53)
# 字体
pr.font.name = "Arial"
大功告成,table的给中属性和实际使用吻合很高。
在对chart进行操作时,可以得到chart本身的属性。于是直接查看chart的属性和方法:
for shape in page.shapes:
if shape.has_chart:
chart = shape.chart
print(dir(chart))
chart_data = ChartData(chart)
cart = chart_data.categories
for s in cart:
print(s)
print(s.label)
for s in chart.series:
print(s.data_labels.show_value)
print(s.name)
可以找到chart有如下属性:
‘category_axis’, ‘chart_style’, ‘chart_title’, ‘chart_type’, ‘element’, ‘font’, ‘has_legend’, ‘has_title’, ‘legend’, ‘part’, ‘plots’, ‘replace_data’, ‘series’, ‘value_axis’
这些和Python中的图都很相似,于是我尝试获取到了图表中的值也惊奇的发现,视乎离成功更近一步了。下一步就是想如何改变这些值成新的值了。于是我发现了replace_data方法,是否运用replace方法就可以完成操作了,于是我查找了相关replace的使用方式,通过不断努力发现数据可以该但是并不是我想要的样子。没有了之前的动态显示过程,操作过程也比较复杂,当我在ppt编辑数据时,原数据居然没有变化,我深知这事还没有结束。于是我去查找API发现其中这样一个描述
class pptx.chart.series.AreaSeries
A data point series belonging to an area plot.
...
values
Read-only. A sequence containing the float values for this series, in the order they appear on the chart.
也就是说我获取到的图表区,部分参数是只读的,仔细一想图表是原数据的一种可视化表现,这里的chart对象只是图表的展示对象去,其数据也相当于一个备份,改变chart属性时并没有实际改变源数据,所以只会在图形上一时的显示更改后的效果,不说这个过程有多复杂,最终也不能实际完成工作。
于是我在这里卡住了。
由于上述问题一时卡住了我,我就先去忙其他的活了,可这终究是一到坎,我想着越过它。于是顺着上面的思路,加上实际的操作,每次人工操作都是点击编辑数据之后,复制粘贴来完成。这样一想,python读了了ppt文件,将其转换为一个对象,表格,可以看得见的图标,都可以在这个对象中找到,那编辑数据处的原数据应该也在原始的shape对象中。我得找到它。
我查看了chart_part属性,这让我眼前一亮[‘before_marshal’, ‘blob’, ‘chart’, ‘chart_workbook’, ‘content_type’, ‘drop_rel’, ‘load’, ‘load_rel’, ‘new’, ‘package’, ‘part’, ‘part_related_by’, ‘partname’, ‘partname_template’, ‘relate_to’, ‘related_parts’, ‘rels’, ‘target_ref’]
这其中就有chart,我欣喜的是发现了chart_workbook,这让我想起了Excel,python中操作Excel的openpyxl中就有workbook。于是我得好好的找找如何修改这个Excel文件的方法。
初步我发现了chart_workbook有如下属性和方法
class ChartWorkbook(builtins.object)
| ChartWorkbook(chartSpace, chart_part)
|
| Provides access to the external chart data in a linked or embedded Excel
| workbook.
|
| Methods defined here:
|
| __init__(self, chartSpace, chart_part)
| Initialize self. See help(type(self)) for accurate signature.
| # 使用xlsx文件的blob格式数据更新这个workbook
| update_from_xlsx_blob(self, xlsx_blob)
| Replace the Excel spreadsheet in the related |EmbeddedXlsxPart| with
| the Excel binary in *xlsx_blob*, adding a new |EmbeddedXlsxPart| if
| there isn't one.
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| xlsx_part
| Return the related |EmbeddedXlsxPart| object having its rId at
| `c:chartSpace/c:externalData/@rId` or |None| if there is no
| `<c:externalData>` element.
发现chart_workbook中有一个blob二进制文件,这里经过不断尝试确定excel的内容就是以二进制存在blob中,如此凭着试一试的心态,我以二进制形式读了一个xlsx文件,然后结合update_from_xlsx_blob方法,想着能不能更新数据。
wbda = open("1111.xlsx","rb").read()
# 遍历每一个幻灯片的形状
for shape in pager.shapes:
# print(shape.has_chart)
if shape.has_chart:
# print(help(shape))
chart_part = shape.chart_part
chart_workbook = chart_part.chart_workbook
chart_workbook.update_from_xlsx_blob(wbda)
chart = shape.chart
# 是否能直接更新表格不需要人工处理,实际并不能
# chart.__init__(chartSpace=chart._chartSpace,chart_part=chart_part)
运行完成,打开新生产的文件,第一眼发现折线图并没有改变,觉得自己又失败了,可当我点击编辑数据后,折线图立刻发生了变化,播放PPT原来的折线图的动态过程还在。总算是大功告成了,唯一美中不足的就是最后还需要手动点一下编辑数据。当然这个可以交个Python去做,暂时想到的就是Python模拟人去点击,相关的知识下次再分享了。
本次使用Python借助PPT模板自动生成同样样式的PPT。这其中走了很多弯路,让我学会了很多东西,强化了对一些知识的认识。
1:解决问题时要有不同的思路,目的是生成同样样式的PPT,那么可以尝试自己直接用Python生成,也可以直接对已经完成的模板进行修改。亦或者是不直接操作使用python-pptx库,使用自动化控制软件去模拟人的操作行为,等等,只要最终问题得到了解决那就已经是一种进步
2:一切皆对象,对象就有属性和方法,通过阅读API文档并结合实际操作可以快速的了解一个库。而实际对象转换时,肉眼看的见的数据或者图形都有可能转换成python中的对象,那么实际ppt所包含的字体,颜色,变宽,大小,图形,表格等都会在python转换的对象中存在,结合实际问题,就可以合理运用属性和方法来快速掌握一个库的使用。完成任务。