1.1. 前言
家喻户晓,安服工程师 又叫做 Word 工程师,在打工或者批量 SRC 的时候,如果产出很多,又须要一个一个的写报告的状况下会十分的折磨人,因而查了一些相干的材料,发现应用 python 的docxtpl
库批量写报告成果很不错,记录一下。
1.2. 介绍
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
1.3. 根底应用
from docxtpl import DocxTemplate
doc = DocxTemplate("test.docx")
context = {'whoami': "d4m1ts"}
doc.render(context)
doc.save("generated_doc.docx")
其中,test.docx 内容如下:
生成后的后果如下:
1.4. 案例介绍
1.4.1. 需要假如
写一份不思考好看的漏扫报告,须要有统计后果图和破绽详情,每个破绽包含破绽名、破绽地址、破绽等级、破绽危害、复现过程、修复倡议六个局部。
1.4.2. 模板文档筹备
编写的模板文档如下,应用到了常见的 if
、for
、 赋值
等,保留为template.docx
,后续只须要向外面填充数据即可。
1.4.3. 数据结构剖析
传入数据须要一串 json 字符串,因而咱们依据模板文档梳理好 json 构造,而后传入即可。
梳理好的数据结构如下:
{
"饼图": "111",
"柱状图": "222",
"破绽简报": [
{
"破绽名": "测试破绽名 1",
"破绽等级": "高危"
}
],
"破绽详情": [
{
"破绽名": "测试破绽名 1",
"破绽地址": "http://blog.gm7.org/",
"破绽等级": "高危",
"破绽危害": "危害 XXX",
"复现过程": "先 xxx,再 xxx,最初 xxx",
"修复倡议": "更新到最新版本即可"
}
]
}
编写代码测试一下可行性:
from docxtpl import DocxTemplate
doc = 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")
很好,达到了预期的成果。
1.4.4. 退出图表
在下面的过程中,内容简直是没问题了,然而图表还是没有展现进去。生成图表咱们应用 plotly
这个库,并将生成内容写入ByteIO
。
相干代码如下:
import plotly.graph_objects as go
from io import BytesIO
def generatePieChart(title: str, labels: list, values: list, colors: list):
"""
生成饼图
https://juejin.cn/post/6911701157647745031#heading-3
https://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_io
def generateBarChart(title: str, x: list, y: list):
"""
生成柱状图
https://cloud.tencent.com/developer/article/1817208
https://blog.csdn.net/qq_25443541/article/details/115999537
https://blog.csdn.net/weixin_45826022/article/details/122912484
:param title: 题目
:param x: 柱状图标签
:param y: 柱状图数据
:return:
"""
# x 轴长度最为 18
b = x
x = []
for i in b:
if len(i) >= 18:
x.append(f"{i[:15]}...")
else:
x.append(i)
# 根底柱状图
fig = go.Figure(data=[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
1.4.5. 最终后果
要插入图片内容,代码语法如下:
myimage = InlineImage(tpl, image_descriptor='test_files/python_logo.png', width=Mm(20), height=Mm(10))
残缺代码如下:
from docxtpl import DocxTemplate, InlineImage
from docx.shared import Mm
import plotly.graph_objects as go
from io import BytesIO
def generatePieChart(title: str, labels: list, values: list, colors: list):
"""
生成饼图
https://juejin.cn/post/6911701157647745031#heading-3
https://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_io
def generateBarChart(title: str, x: list, y: list):
"""
生成柱状图
https://cloud.tencent.com/developer/article/1817208
https://blog.csdn.net/qq_25443541/article/details/115999537
https://blog.csdn.net/weixin_45826022/article/details/122912484
:param title: 题目
:param x: 柱状图标签
:param y: 柱状图数据
:return:
"""
# x 轴长度最为 18
b = x
x = []
for i in b:
if len(i) >= 18:
x.append(f"{i[:15]}...")
else:
x.append(i)
# 根底柱状图
fig = go.Figure(data=[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
doc = 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")
后果如下: