在风电、光伏项目里,我们经常需要输出一份结构相对固定、但数据每次都不同的 Word 报告: 比如“规划容量与发电量计算”“资源评估”“经济性测算”等。

python-docx-template 的作用,就是把这件事变成一条流水线:

Word 模板(.docx) + 数据(YAML / Excel / JSON 等) → 自动生成完整报告

下面一步步来,从最常用、最关键的几个动作讲起。

安装与最小可用示例

先安装:

pip install docxtpl

最小示例:把“项目名称”写进 Word 模板。

from docxtpl import DocxTemplate

doc = DocxTemplate("templates/report_template.docx")

context = {
    "项目名称": "某地分布式光伏项目",
    "规划容量": "50 MWp",
}

doc.render(context)
doc.save("output/光伏项目自动报告示例.docx")

Word 模板里对应的占位符写成:

本报告针对 {{ 项目名称 }} ,规划容量约为 {{ 规划容量 }}。

到这一步,你已经完成了最基本的“用 Python 往 Word 里灌数据”。

设计 Word 模板

基本变量写法

在 Word 中插入变量占位符时,遵守几个简单规则:

  • 必须用花括号包起来:{{ 变量名 }}
  • 指标中间不要有冒号,不要有空格:❌ {{ 屋顶面积 m2 }}
  • 指标名称不能以数字开头:❌ {{ 25年发电量 }}
  • 可以用中文或字母开头,后面跟数字或下划线:✅ {{ 屋顶面积_m2 }}{{ 年均满发小时数 }}

推荐风格:

{{ 项目名称 }}
{{ 屋顶面积_m2 }}
{{ 规划容量_MWp }}
{{ 年均发电量_万千瓦时 }}

在表格中迭代“逐月发电量 / 辐射量”

典型需求:在报告中列一个“斜面辐射量逐月统计表”或“25 年发电量衰减表”。

在 Word 里建好表头,然后在数据行那一行写模板语法:

{% for row in 斜面辐射量_逐月 %}
    {{ row.月份 }}    {{ row.水平总辐射量 }}    {{ row.散射辐射量 }}    {{ row.直接辐射量 }}
{% endfor %}

对应的 Python 数据结构(注意只给关键字段):

context = {
    "斜面辐射量_逐月": [
        {"月份": 1, "水平总辐射量": 73, "散射辐射量": 28, "直接辐射量": 108},
        {"月份": 2, "水平总辐射量": 89, "散射辐射量": 39, "直接辐射量": 103},
        # ... 按月追加即可
    ],
}

模板里只需要记住一句话: “我在 Word 里写 for row in xxx,Python 这边就给一个列表 xxx。”

从 YAML / Excel 读取数据并组装 context

实际项目里,数据往往已经在 YAML 或 Excel 里算好了。 我们要做的,就是把它们读出来,整理成 context 字典。

从 YAML 读取基础信息

假设有一个 YAML 文件里保存了项目的基础信息和资源数据,可以这样读:

import yaml
from pathlib import Path

def load_yaml_data(path: str) -> dict:
    with open(path, "r", encoding="utf-8") as f:
        return yaml.safe_load(f)

raw = load_yaml_data("data/project.yaml")

context = {
    # 基础信息
    "项目名称": raw.get("项目名称", "某风光项目"),
    "公司名称": raw.get("公司名称", "某能源科技有限公司"),
    "咨询时间": raw.get("咨询时间", ""),
    "项目位置": raw.get("项目位置", ""),
    # 资源指标
    "总辐射量kWh": raw.get("总辐射量kWh", ""),
    "资源等级": raw.get("资源等级", ""),
    # 容量与发电量
    "屋顶面积_m2": raw.get("屋顶面积_m2", ""),
    "规划容量": raw.get("规划容量", ""),
    "年均发电量": raw.get("年均发电量", ""),
}

你不需要一次性把所有键都塞进去,只挑报告真正用到的字段就好。

从 Excel 中读取“计算结果表”

容量、发电量、多年衰减这些,通常会先在 Excel 里算一遍,再导入报告。

pandas 读一个简单的“发电量计算”工作表,转换为模板可用的数据 (如果表的结构本身很复杂,使用 openpyxl 也是一个不错的选择):

import pandas as pd

def load_generation_sheet(path: str) -> list[dict]:
    df = pd.read_excel(path, sheet_name="发电量计算")
    # 只保留关键列,改一下列名方便模板使用
    df = df[["年份", "效率", "实际发电量", "等效利用小时数"]]
    return df.to_dict(orient="records")

generation_rows = load_generation_sheet("data/规划容量与发电量计算.xlsx")

context["发电量计算列表"] = generation_rows

Word 模板中的表格这样写:

{% for row in 发电量计算列表 %}
{{ row.年份 }} 年    {{ row.效率 }}    {{ row.实际发电量 }} 万千瓦时    {{ row.等效利用小时数 }} 小时
{% endfor %}

整体流程

把前面几步串起来,就是一个完整的自动报告流程。

from docxtpl import DocxTemplate

def build_context():
    raw = load_yaml_data("data/project.yaml")
    generation_rows = load_generation_sheet("data/规划容量与发电量计算.xlsx")

    return {
        # 基础信息
        "项目名称": raw.get("项目名称", "某风电/光伏项目"),
        "公司名称": raw.get("公司名称", "某能源科技公司"),
        "咨询时间": raw.get("咨询时间", ""),
        # 资源与容量
        "总辐射量kWh": raw.get("总辐射量kWh", ""),
        "规划容量": raw.get("规划容量", ""),
        "年均发电量": raw.get("年均发电量", ""),
        # 列表类数据
        "发电量计算列表": generation_rows,
        "斜面辐射量_逐月": raw.get("斜面辐射量", {}).get("逐月", []),
    }

def render_report():
    doc = DocxTemplate("templates/规划容量与发电量计算模板.docx")
    context = build_context()
    doc.render(context)
    doc.save("output/自动生成的风光项目报告.docx")

if __name__ == "__main__":
    render_report()

实际使用中,你只需要换掉:

  • 模板路径
  • 数据文件路径
  • 输出文件路径

就能在不同项目间复用同一套代码。

条件判断

有些章节是“可选”的,比如:

  • 是否写“气象要素详细分析”
  • 是否展示“多场景敏感性分析”

可以在模板里用 if 控制段落是否出现。

Word 模板中

{% if 气象要素 %}
本节简要介绍项目所在区域的气象特征:

{{ 气象要素 }}
{% endif %}

Python 里控制

context["气象要素"] = raw.get("气象要素")  # 没有就为 None,块会自动不显示

这比“先在 Python 拼接一大段字符串”要轻松很多,也更贴近写文档的思路。

插入发电量曲线 / 辐射分布图

python-docx-template 支持把图片当成变量塞进去,很适合插入:

  • 年发电量变化曲线
  • 月总辐射量柱状图
  • 项目区位图/用地示意图

模板中占位符

{{ 年发电量曲线图 }}

Python 里填充图片

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

doc = DocxTemplate("templates/report_template.docx")

context = {
    # 其他变量...
    "年发电量曲线图": InlineImage(
        doc,
        "assets/project/images/generation_curve.png",
        height=Mm(70),
    )
}

doc.render(context)
doc.save("output/带图片的报告示例.docx")

可以按“项目 ID / 项目简称”组织图片路径,报告生成时只需要换一组路径即可。

数据缺失时的兜底策略

风电 / 光伏项目数据不一定一次性齐全,有些字段缺了,模板里直接空白会很尴尬。 常用做法是设置“缺失文本”,让读者一眼就知道数据没填上,而不是以为漏写了。

简单版本可以在组装 context 时做:

def get_or_missing(raw: dict, key: str) -> str:
    return raw.get(key) or f"%数据缺失:{key}%"

context = {
    "项目名称": get_or_missing(raw, "项目名称"),
    "规划容量": get_or_missing(raw, "规划容量"),
    "总辐射量kWh": get_or_missing(raw, "总辐射量kWh"),
}

在 Word 里就会显示类似:

规划容量:%数据缺失:规划容量%

方便你在人工校核时快速定位问题。

常见小坑与检查清单

最后,给自己留一份“踩坑清单”,免得每次都忘:

  1. 变量命名

    • 中间不要有空格、冒号
    • 不要以数字开头
    • 中文 / 英文都可以,用下划线分隔词更清晰
  2. Word 模板里的空格

    • 占位符里不要多打空格,建议写成 {{变量名}}{{ 变量名 }},整体保持一致。
  3. 列表字段为空

    • 如果类似 发电量计算列表 是空列表,循环不会输出任何行,表格会只剩表头——这是预期行为。
    • 真正要避免的是:字段是 None 或漏掉,这时模板容易报错,建议统一给空列表。
  4. 模板调试方法

    • 可以先在模板里只放几个关键变量,跑通一版,确认路径和变量名都没问题,再一点一点把表格、图片、条件块加回来。