前言
众所周知 , 安服工程师又叫做Word工程师,在打工或者批量SRC的时候,如果产出很多,又需要一个一个的写报告的情况下会非常的折磨人,因此查了一些相关的资料,发现使用python的docxtpl库批量写报告效果很不错 , 记录一下 。介绍
docxtpl 是一个用于生成 Microsoft Word 文档的模板引擎库,它结合了 docx 模块和 Jinja2 模板引擎,使用户能够使用 Microsoft Word 模板文件并在其中填充动态数据 。它提供了一种方便的方式来生成个性化的 Word 文档,并支持条件语句、循环语句和变量等控制结构,以满足不同的文档生成需求 。官方GitHub地址:https://github.com/elapouya/python-docx-template
官方文档地址:https://docxtpl.readthedocs.io/en/latest/
简单来说:就是创建一个类似Jinja2语法的模板文档,然后往里面动态填充内容就可以了安装:
pip3 install docxtpl基础使用
from docxtpl import DocxTemplatedoc = DocxTemplate("test.docx")context = {'whoami': "d4m1ts"}doc.render(context)doc.save("generated_doc.docx")其中,test.docx内容如下:

生成后的结果如下:

案例介绍
需求假设
写一份不考虑美观的漏扫报告,需要有统计结果图和漏洞详情,每个漏洞包括漏洞名、漏洞地址、漏洞等级、漏洞危害、复现过程、修复建议六个部分 。模板文档准备
编写的模板文档如下,使用到了常见的if、for、赋值等,保存为template.docx(点击可下载),后续只需要向里面填充数据即可 。
template
数据结构分析
传入数据需要一串json字符串,因此我们根据模板文档梳理好json结构,然后传入即可 。梳理好的数据结构如下:
{"饼图": "111","柱状图": "222","漏洞简报": [{"漏洞名": "测试漏洞名1","漏洞等级": "高危"}],"漏洞详情": [{"漏洞名": "测试漏洞名1","漏洞地址": "http://blog.gm7.org/","漏洞等级": "高危","漏洞危害": "危害XXX","复现过程": "先xxx,再xxx,最后xxx","修复建议": "更新到最新版本即可"}]}编写代码测试一下可行性:
from docxtpl import DocxTemplatedoc = DocxTemplate("template.docx")context = {"饼图": "111","柱状图": "222","漏洞简报": [{"漏洞名": "测试漏洞名1","漏洞等级": "高危"},{"漏洞名": "测试漏洞名2","漏洞等级": "严重"},{"漏洞名": "测试漏洞名2","漏洞等级": "中危"}],"漏洞详情": [{"漏洞名": "测试漏洞名1","漏洞地址": "http://blog.gm7.org/","漏洞等级": "高危","漏洞危害": "危害XXX","复现过程": "先xxx,再xxx,最后xxx","修复建议": "更新到最新版本即可"},{"漏洞名": "测试漏洞名2","漏洞地址": "http://bblog.gm7.org/","漏洞等级": "严重","漏洞危害": "危害XXX","复现过程": "先xxx,再xxx,最后xxx","修复建议": "更新到最新版本即可"},{"漏洞名": "测试漏洞名3","漏洞地址": "http://cblog.gm7.org/","漏洞等级": "中危","漏洞危害": "危害XXX","复现过程": "先xxx , 再xxx , 最后xxx","修复建议": "更新到最新版本即可"}]}doc.render(context)doc.save("generated_doc.docx")很好,达到了预期的效果 。

res
加入图表
在上面的过程中,内容几乎是没问题了,但是图表还是没有展示出来 。生成图表我们使用plotly这个库,并将生成内容写入ByteIO 。相关代码如下:
import plotly.graph_objects as gofrom io import BytesIOdef generatePieChart(title: str, labels: list, values: list, colors: list):"""生成饼图https://juejin.cn/post/6911701157647745031#heading-3https://juejin.cn/post/6950460207860449317#heading-5:param title: 饼图标题:param labels: 饼图标签:param values:饼图数据:param colors:饼图每块的颜色:return:"""# 基础饼图fig = go.Figure(data=[go.Pie(labels=labels,values=values,hole=.4,# 中心环大小insidetextorientation="horizontal")])# 更新颜色fig.update_traces(textposition='inside',# 文本显示位置hoverinfo='label percent',# 悬停信息textinfo='label percent', # 饼图中显示的信息textfont_size=15,marker=dict(colors=colors))# 更新标题fig.update_layout(title={# 设置整个标题的名称和位置"text": title,"y": 0.96,# y轴数值"x": 0.5,# x轴数值"xanchor": "center",# x、y轴相对位置"yanchor": "top"})image_io = BytesIO()fig.write_image(image_io, format="png")return image_iodef generateBarChart(title: str, x: list, y: list):"""生成柱状图https://cloud.tencent.com/developer/article/1817208https://blog.csdn.net/qq_25443541/article/details/115999537https://blog.csdn.net/weixin_45826022/article/details/122912484:param title: 标题:param x: 柱状图标签:param y:柱状图数据:return:"""# x轴长度最为18b = xx = []for i in b:if len(i) >= 18:x.append(f"{i[:15]}...")else:x.append(i)# 基础柱状图fig = go.Figure(data=https://www.itzhengshu.com/word/[go.Bar(x=x,y=y,text=y,textposition="outside",marker=dict(color=["#3498DB"] * len(y)),width=0.3)])# 更新标题fig.update_layout(title={# 设置整个标题的名称和位置"text": title,"y": 0.96,# y轴数值"x": 0.5,# x轴数值"xanchor": "center",# x、y轴相对位置"yanchor": "top"},xaxis_tickangle=-45,# 倾斜45度plot_bgcolor='rgba(0,0,0,0)'# 背景透明)fig.update_xaxes(showgrid=False)fig.update_yaxes(zeroline=True,zerolinecolor="#17202A",zerolinewidth=1,showgrid=True,gridcolor="#17202A",showline=True)image_io = BytesIO()fig.write_image(image_io, format="png")return image_io最终结果
要插入图片内容,代码语法如下:myimage = InlineImage(tpl, image_descriptor='test_files/python_logo.png', width=Mm(20), height=Mm(10))完整代码如下:
from docxtpl import DocxTemplate, InlineImagefrom docx.shared import Mmimport plotly.graph_objects as gofrom io import BytesIOdef generatePieChart(title: str, labels: list, values: list, colors: list):"""生成饼图https://juejin.cn/post/6911701157647745031#heading-3https://juejin.cn/post/6950460207860449317#heading-5:param title: 饼图标题:param labels: 饼图标签:param values:饼图数据:param colors:饼图每块的颜色:return:"""# 基础饼图fig = go.Figure(data=[go.Pie(labels=labels,values=values,hole=.4,# 中心环大小insidetextorientation="horizontal")])# 更新颜色fig.update_traces(textposition='inside',# 文本显示位置hoverinfo='label percent',# 悬停信息textinfo='label percent',# 饼图中显示的信息textfont_size=15,marker=dict(colors=colors))# 更新标题fig.update_layout(title={# 设置整个标题的名称和位置"text": title,"y": 0.96,# y轴数值"x": 0.5,# x轴数值"xanchor": "center",# x、y轴相对位置"yanchor": "top"})image_io = BytesIO()fig.write_image(image_io, format="png")return image_iodef generateBarChart(title: str, x: list, y: list):"""生成柱状图https://cloud.tencent.com/developer/article/1817208https://blog.csdn.net/qq_25443541/article/details/115999537https://blog.csdn.net/weixin_45826022/article/details/122912484:param title: 标题:param x: 柱状图标签:param y:柱状图数据:return:"""# x轴长度最为18b = xx = []for i in b:if len(i) >= 18:x.append(f"{i[:15]}...")else:x.append(i)# 基础柱状图fig = go.Figure(data=https://www.itzhengshu.com/word/[go.Bar(x=x,y=y,text=y,textposition="outside",marker=dict(color=["#3498DB"] * len(y)),width=0.3)])# 更新标题fig.update_layout(title={# 设置整个标题的名称和位置"text": title,"y": 0.96,# y轴数值"x": 0.5,# x轴数值"xanchor": "center",# x、y轴相对位置"yanchor": "top"},xaxis_tickangle=-45,# 倾斜45度plot_bgcolor='rgba(0,0,0,0)'# 背景透明)fig.update_xaxes(showgrid=False)fig.update_yaxes(zeroline=True,zerolinecolor="#17202A",zerolinewidth=1,showgrid=True,gridcolor="#17202A",showline=True)image_io = BytesIO()fig.write_image(image_io, format="png")return image_iodoc = DocxTemplate("template.docx")context = {"饼图": InlineImage(doc, image_descriptor=generatePieChart(title="漏洞数量",labels=["严重", "高危", "中危", "低危"],values=[1, 1, 1, 0],colors=["#8B0000", "red", "orange", "aqua"]), width=Mm(130)),"柱状图": InlineImage(doc, image_descriptor=generateBarChart(title="漏洞类型",x=["测试漏洞名1", "测试漏洞名2", "测试漏洞名3"],y=[1, 1, 1]), width=Mm(130)),"漏洞简报": [{"漏洞名": "测试漏洞名1","漏洞等级": "高危"},{"漏洞名": "测试漏洞名2","漏洞等级": "严重"},{"漏洞名": "测试漏洞名2","漏洞等级": "中危"}],"漏洞详情": [{"漏洞名": "测试漏洞名1","漏洞地址": "http://blog.gm7.org/","漏洞等级": "高危","漏洞危害": "危害XXX","复现过程": "先xxx,再xxx , 最后xxx","修复建议": "更新到最新版本即可"},{"漏洞名": "测试漏洞名2","漏洞地址": "http://bblog.gm7.org/","漏洞等级": "严重","漏洞危害": "危害XXX","复现过程": "先xxx,再xxx,最后xxx","修复建议": "更新到最新版本即可"},{"漏洞名": "测试漏洞名3","漏洞地址": "http://cblog.gm7.org/","漏洞等级": "中危","漏洞危害": "危害XXX","复现过程": "先xxx,再xxx,最后xxx","修复建议": "更新到最新版本即可"}]}doc.render(context)doc.save("generated_doc.docx")结果如下:

result
【快速生成定制化的Word文档:Python实践指南】- END -
