Python+docxtpl+plotly实现模板word文档图表生成

3,495 阅读4分钟

依赖

关于依赖包的安装请看文档

  1. docxtpl 文档地址: docxtpl.readthedocs.io/en/latest/
    注:它的github中有很多example实例,建议下载该项目在本地运行学习各种使用场景。

  2. plotly

文档地址: plotly.com/python/
注: 导出静态图像需要依赖安装kaleido包,直接pip安装即可,具体内容可在plotly文档中查看到。

  1. Jinja2

它的文档就不放了...


这里我再对依赖的所有库和版本做一个汇总,避免版本之间不兼容的问题:

  • docxtpl==0.11.2
  • kaleido==0.1.0
  • plotly==4.14.1
  • jinja2==2.10.3
  • lxml==4.3.2
  • python-docx==0.8.10

需求场景分析

最近有个新的接口需求,需要后端提供一个报表导出接口,该接口可以导出两种内容形式的word文档,文档内包含有图表数据:统计表格以及数据分析的折线图、柱状图以及饼图。

由于word的导出内容,其格式及描述信息基本是固定的,只有数据和图表会动态变化,因此调研后决定使用docxtpl库+Jinja2的方式使用模板语法对制作好的word模板文档进行内容填充生成最终文件,docxtpl库是依赖python-docx实现的,可以在制作好的模板文件中通过jinja2渲染字典,插入文字、表格、图片等数据。

虽然我们平时使用word的时候可以根据数据生成图表,但是python-docx库本身是没有生成图表的API接口的,图表数据只能插入静态图像的方式来实现。经过调研和测试,最终选用了plotly这个包来进行图表生成和图像文件导出。

网上很多方案是用的pyecharts+pyecharts-snapshot的方式,实际是采用无头浏览器渲染截图后得到的静态图像文件,实际测试中导出图片效率相对太低,且依赖node环境phantomjs,使用体验也不佳,不推荐。

准备模板docx文档

先使用jinja2制作好作为基础模板的word文档,貌似不能上传文件,我这里就简单截图示例吧,下图为示例docx文件中编辑的模板内容,已经包含了我实际项目中用到的填充方式:普通填充、表格循环填充、条件判断内容块,这里都是使用jinja2的语法,对jinja2不熟悉可以大概先找文档看下这个模板语法的使用方式。

plotly图表静态文件生成代码示例

这里只是简单展示一下使用plotly生成本地文件或者将静态文件数据写入ByteIO,其他图表相关的API请参考文档,官方文档非常详细。

import plotly.graph_objects as go
from io import BytesIO

def get_line_image(x, y):
    fig = go.Figure()
    fig.add_trace(go.Scatter(x=x, y=y, text=y, textposition="top center", mode="lines+markers"))
    image_io = BytesIO()
    fig.write_image(image_io, format="jpeg")
    return image_io

由于我后端并不需要图像文件生成到本地,因此只需要将文件对象写入到BytesIO返回即可, 你也可以根据需求直接write_image(file="a.png")的方式生成图片及返回本地文件路径。

docxtpl模板库的使用示例

最终我们要将组装的填充数据写入到docx文档中,生成需要的word文档。 还是直接上示例代码吧。

from docxtpl import DocxTemplate, InlineImage
from docx.shared import Mm


# 初始化模板处理对象
tpl = DocxTemplate("./示例模板文件.docx")

# 图表静态文件对象调用上面的示例plotly方法生成
line_charts = get_line_image(x=["一班", "二班", "三班"], y=["75", "80", "82"])

# 待填充的示例字典数据,其中key对应word模板中的填充名,图片需要调用InlineImage类
context = {
    "average": 80,
    "class": "三年2班",
    "table": [{"name": "小红", "gender": "男", "grade": 80},
              {"name": "小白", "gender": "男", "grade": 79},
              {"name": "小黑", "gender": "女", "grade": 81}],
    "line_image": InlineImage(tpl, line_charts, width=Mm(164), height=Mm(82.5)),

}
# 开始渲染context数据到模板文件中
tpl.render(context=context)
# 保存:这里依然可以写入的ByteIO对象,因为后端不需要保存文件,你也依然可以根据需要使用save('a.docx')的方式保存最终文件到本地
# file_io = BytesIO()
tpl.save("示例结果文档.docx")

一下是上述示例的输出结果截图:

最后

只要参照以上跑通了一次,就能根据实际需求重新设计代码结构以及调整模板文件格式、图片大小等,基本能实现模板类的word文档导出需求,若有任何问题和建议,也欢迎大家留言,希望这篇文档能帮到你。