Python快速生成定制化的Word(docx)文档
作者:初始安全 发布时间:2022-04-27 12:33:06
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")
结果如下:
来源:https://segmentfault.com/a/1190000043792745
猜你喜欢
- 一、开发环境Python版本:3.6.4相关模块:pygame模块;以及一些Python自带的模块。二、环境搭建安装Python并添加到环境
- 题目:请求出任意区间[a,b]的所有素数,简单考虑实用性这道题看起来应该很easy是吧,但任意区间(这个问题有没get 到)Afanty的分
- 摸到她了!青翠的衣衫,奶白的肌肤,捧在手上的感觉真是太好了,心里美滋滋的。《悟透JavaScript》,一本偶然之作,终于成书并出版了。本书
- 1.字符强转一切如果是字符串进行强制转换, 仅仅就是在原数据类型的两边套上引号2.list : 强制转换成列表""&qu
- 以下是YUI中不建议用的一些说明: 表达式的问题就在于它的计算频率要比
- 前言经典面试题: 判断一个字符串里面的括号是否闭合,如:{{()}} 就是一个闭合的字符串。{{()}]} 这个里面 ([)] 括号不对称,
- 一、问题说明首先,运行下述代码,复现问题:# -*-coding:utf-8-*-import reimport requestsfrom
- 语法: text-overflow : clip | ellipsis 参数: clip : 不显示省略标记(...),而是简单的裁切 el
- Postman生成okhttp代码依赖<dependency>  
- 回车和换行的历史:机械打字机有回车和换行两个键作用分别是:换行就是把滚筒卷一格,不改变水平位置。 (即移到下一行,但不是行首,而是和上一行水
- 本文介绍了三种跨域访问的方法,php,asp及jsp种访问远程文件的方法。这几天脑细胞剩下的不多了,不过问题都一个个解决了。我希望搜索引擎能
- 英文版见:http://dflying.dflying.net/.../98_web_standard_and_aspnet__part1_
- 代码如下:--创建测试表 DECLARE @Users TABLE ( ID INT IDENTITY(1,1), UserIn
- Q:Dreamweaver(简称DW)中如何输入空格?A:按下“Ctrl+Shift+空格键”或者在中
- 临近下班的时候,突然想到统计热门文章的问题。以前我所知道的热门文章统计,基本有这么几种:按点击数排序 该方法最大的问题在于热门的文章会越来越
- global 属性返回 Boolean 值,指出正则表达式使用的global 标志 (g) 的状态。默认值为 false。只读。rgExp.
- 首先来看一个封装的curl函数function request_post($url = '', $param = '
- 技巧之一:提高使用Request集合的效率 访问一个ASP集合来提取一个值是费时的、占用计算资源的过程。因为这个操作包含了一系列对相关集合的
- 用途:图片经常使用onload来改变宽度,但这样会出现图片的闪烁,这个简单的类就是用来解决这个问题的。函数loadImage,用来加载图片,
- dictPython内置了字典:dict的支持,dict全称dictionary,在其他语言种也称为map,使用键-值(key-value)