使用 Python 根据自定义的 Word 模板和传入的 JSON 数据生成 Word 报告,是自动化文档生成的常见需求。最常用的方法是使用 python-docx
和 docxtpl
库。其中,docxtpl
是基于 python-docx
的模板引擎,支持 Jinja2 模板语法,非常适合根据模板和数据生成报告。
1. 新建项目、引入依赖
pip install docxtpl
2. 创建word模板
3. 程序导出word文档
from datetime import datetime
from docxtpl import DocxTemplate
import json
def generate_report(template_path, json_data, output_path):
# 加载模板
doc = DocxTemplate(template_path)
# 加载 JSON 数据
context = json.loads(json_data) if isinstance(json_data, str) else json_data
# 渲染模板
doc.render(context)
# 保存为新文件
doc.save(output_path)
print(f"报告已生成:{output_path}")
# 使用示例
if __name__ == "__main__":
template_file = "./template/test-tempalte01.docx"
output_file = "./output/test-output01.docx"
# 模拟 JSON 数据
data = {
"report_title": "测试报告",
"report_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"server_infos": [
{"server_name": "server01", "ip": "192.168.10.5", "port": 80},
{"server_name": "server02", "ip": "192.168.10.6", "port": 80},
{"server_name": "server03", "ip": "192.168.10.7", "port": 80}
]
}
generate_report(template_file, data, output_file)
4. 程序导出PDF
安装依赖
pip install docx2pdf
修改程序
from datetime import datetime from docxtpl import DocxTemplate from docx2pdf import convert import json def generate_report(template_path, json_data, output_path, export_pdf=False): # 加载模板 doc = DocxTemplate(template_path) # 加载 JSON 数据 context = json.loads(json_data) if isinstance(json_data, str) else json_data # 渲染模板 doc.render(context) # 保存为Word文件 doc.save(output_path) print(f"Word报告已生成:{output_path}") # 如果需要导出PDF if export_pdf: pdf_path = output_path.replace('.docx', '.pdf') convert(output_path, pdf_path) print(f"PDF报告已生成:{pdf_path}") return pdf_path return output_path # 使用示例 if __name__ == "__main__": template_file = "./template/test-tempalte01.docx" output_file = "./output/test-output01.docx" # 模拟 JSON 数据 data = { "report_title": "测试报告", "report_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "server_infos": [ {"server_name": "server01", "ip": "192.168.10.5", "port": 80}, {"server_name": "server02", "ip": "192.168.10.6", "port": 80}, {"server_name": "server03", "ip": "192.168.10.7", "port": 80} ] } generate_report(template_file, data, output_file, export_pdf=True)
此时会同时导出word和pdf文件
注意:使用docx2pdf会依赖Microsoft Office,所以在Windows环境下可以转换成pdf文件,但是在Linux环境会有问题。需要修改为LibreOffice方案。详见下面步骤。
5. 将程序导出为工具使用(windows)
安装LibreOffice
下载地址:https://www.libreoffice.org/download/download-libreoffice/
安装完成后,将安装目录的program目录添加到Path环境变量。
# 验证 soffice --version
修改程序
from datetime import datetime from docxtpl import DocxTemplate import json import argparse import sys import os import subprocess def generate_report(template_path, json_data, output_path, export_pdf=False): # 转换路径为绝对路径并规范化 template_path = os.path.abspath(template_path) output_path = os.path.abspath(output_path) print(f"执行中, 正在检查模板文件路径: {template_path}") if not os.path.exists(template_path): raise FileNotFoundError(f"执行异常, 模板文件不存在!请检查路径是否正确。\n路径: {template_path}") if not os.path.isfile(template_path): raise FileNotFoundError(f"执行异常, 路径存在,但不是一个文件(可能是目录)。\n路径: {template_path}") file_size = os.path.getsize(template_path) if file_size == 0: raise ValueError(f"执行异常, 模板文件大小为 0,可能是空文件。\n路径: {template_path}") print(f"执行中, 模板文件存在,大小: {file_size} 字节") # === 确保输出目录存在 === output_dir = os.path.dirname(output_path) # 根据输出文件的路径获取父目录(导出文件的目录) if output_dir and not os.path.exists(output_dir): print(f"执行中, 输出目录不存在,正在创建: {output_dir}") os.makedirs(output_dir, exist_ok=True) # 递归创建目录 # ============================= # 加载模板 try: doc = DocxTemplate(template_path) except Exception as e: raise RuntimeError(f"加载模板失败: {e}") # 解析 JSON 数据 try: context = json.loads(json_data) if isinstance(json_data, str) else json_data # 添加当前时间(如果模板中使用了 report_time 但未提供) if 'report_time' not in context: context['report_time'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") except json.JSONDecodeError as e: raise ValueError(f"执行异常, JSON 数据解析失败: {e}") # 渲染模板 try: doc.render(context) except Exception as e: raise RuntimeError(f"执行异常, 模板渲染失败,请检查上下文变量: {e}") # 保存 Word 文件 doc.save(output_path) print(f"执行中, Word报告已生成:{output_path}") # 导出 PDF if export_pdf: try: pdf_path = output_path.replace('.docx', '.pdf') # convert(output_path, pdf_path) convert_to_pdf_with_libreoffice(output_path, pdf_path) print(f"执行中, PDF报告已生成:{pdf_path}") return pdf_path except Exception as e: raise RuntimeError(f"执行异常, 转换PDF失败: {e}") return output_path def convert_to_pdf_with_libreoffice(input_docx, output_pdf): """使用 LibreOffice 将 .docx 转为 .pdf""" # 确保目录存在 pdf_dir = os.path.dirname(output_pdf) if pdf_dir and not os.path.exists(pdf_dir): os.makedirs(pdf_dir, exist_ok=True) # 调用 LibreOffice 命令 try: subprocess.run([ 'soffice', '--headless', '--convert-to', 'pdf', '--outdir', pdf_dir, input_docx ], check=True, capture_output=True) # LibreOffice 会生成在指定目录,文件名相同,扩展名为 .pdf generated_pdf = os.path.join(pdf_dir, os.path.basename(input_docx).rsplit('.', 1)[0] + '.pdf') # 移动或重命名为目标路径(避免同名冲突) if generated_pdf != output_pdf: os.replace(generated_pdf, output_pdf) print(f"执行中, PDF转换成功: {output_pdf}") except subprocess.CalledProcessError as e: raise RuntimeError(f"执行异常, LibreOffice 转换失败: {e.stderr.decode()}") except Exception as e: raise RuntimeError(f"执行异常, 调用 LibreOffice 时出错: {e}") def main(): parser = argparse.ArgumentParser(description="从模板和JSON数据生成Word/PDF报告") parser.add_argument( '--template', '-t', required=True, help='Word模板文件路径 (.docx)' ) parser.add_argument( '--output', '-o', required=True, help='输出文件路径,例如: ./output/report.docx' ) parser.add_argument( '--data', '-d', required=True, help='JSON格式的数据字符串,或以@开头的文件路径,如: @data.json' ) parser.add_argument( '--pdf', action='store_true', help='是否同时导出PDF文件' ) args = parser.parse_args() # 支持从文件读取 JSON 数据(以 @ 开头) if args.data.startswith('@'): data_file = args.data[1:] try: with open(data_file, 'r', encoding='utf-8') as f: json_data = f.read() except FileNotFoundError: print(f"执行异常, 数据文件未找到: {data_file}", file=sys.stderr) sys.exit(1) except Exception as e: print(f"执行异常, 读取数据文件失败: {e}", file=sys.stderr) sys.exit(1) else: json_data = args.data # 调用生成函数 try: result_path = generate_report( template_path=args.template, json_data=json_data, output_path=args.output, export_pdf=args.pdf ) print(f"执行完成: {result_path}") except Exception as e: print(f"执行异常, 生成报告失败: {e}", file=sys.stderr) sys.exit(1) if __name__ == "__main__": main()
导出为可执行程序
# 安装依赖 python -m pip install pyinstaller # 导出,此时会生成一个dist目录,可执行文件在此目录中 pyinstaller main.py --onefile --console --clean # 重命名(可选) ren main.exe xxx.exe
组装数据(data.json)
{ "report_title": "测试报告", "server_infos": [ {"server_name": "server01", "ip": "192.168.10.5", "port": 80}, {"server_name": "server02", "ip": "192.168.10.6", "port": 80}, {"server_name": "server03", "ip": "192.168.10.7", "port": 80} ] }
运行exe文件
# windows 数据使用文件的方式 main.exe --template "D:/universal_export/universal_export/template/test-template01.docx" --output "D:/universal_export/universal_export/output/test-template01.docx" --data @D:/universal_export/universal_export/data.json --pdf # windows 数据使用字符串的方式 main.exe --template "D:/universal_export/universal_export/template/test-template01.docx" --output "D:/universal_export/universal_export/output/test-template01.docx" --data "{\"report_title\":\"测试报告\",\"server_infos\":[{\"server_name\":\"server01\",\"ip\":\"192.168.10.5\",\"port\":80},{\"server_name\":\"server02\",\"ip\":\"192.168.10.6\",\"port\":80},{\"server_name\":\"server03\",\"ip\":\"192.168.10.7\",\"port\":80}]}" --pdf
6. 将程序导出为工具使用(Centos)
Centos安装python环境
# 安装依赖 yum -y groupinstall "Development tools" yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel yum install -y libffi-devel zlib1g-dev yum install zlib* -y # 下载源码包 cd /opt sudo wget https://www.python.org/ftp/python/3.12.6/Python-3.12.6.tgz sudo tar -xzf Python-3.12.6.tgz # 创建编译安装目录 mkdir /usr/local/python3 # 检查openssl版本 openssl version # 对于python 3.12,openssl版本不能低于1.1.1 # 添加 EPEL 仓库 yum install -y epel-release # 安装较新的 OpenSSL yum install -y openssl11 openssl11-devel # 安装 export CPPFLAGS="-I/usr/include/openssl11" export LDFLAGS="-L/usr/lib64/openssl11" cd Python-3.12.6 ./configure --prefix=/usr/local/python3 --with-openssl=/usr --with-openssl-include=/usr/include/openssl11 --with-openssl-lib=/usr/lib64/openssl11 --enable-shared make -j$(nproc) sudo make install # 配置库路径 echo '/usr/local/python3/lib' | sudo tee /etc/ld.so.conf.d/python3.conf sudo ldconfig # 创建软链接 ln -s /usr/local/python3/bin/python3 /usr/local/bin/python3 ln -s /usr/local/python3/bin/pip3 /usr/local/bin/pip3 # 验证 python3 -V pip3 -V python3 -c "import ssl; print(ssl.OPENSSL_VERSION)" # 修改pip源 cd ~ mkdir .pip cd .pip vim pip.conf #进入后添加以下内容,保存退出. [global] index-url = https://pypi.tuna.tsinghua.edu.cn/simple
Centos安装libreoffice
# 在线安装 yum install epel-release -y yum install libreoffice-headless -y # 验证 soffice --version
将程序上传至Linux服务器后编译
# 安装 PyInstaller pip3 install pyinstaller # 安装程序所需要的依赖 pip3 install docxtpl # 打包,命名为universal_export python3 -m PyInstaller --onefile --clean --name=universal_export main.py
执行导出命令
./universal_export -t template/test-template01.docx -o out/test.docx -d @data.json --pdf