乐趣区

关于python:快速生成定制化的Word文档Python实践指南

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. 模板文档筹备

编写的模板文档如下,应用到了常见的 iffor 赋值 等,保留为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")

后果如下:

退出移动版