1-python 自定义模板导出文档-基础实现
使用 Python 根据自定义的 Word 模板和传入的 JSON 数据生成 Word 报告,是自动化文档生成的常见需求。最常用的方法是使用 python-docx
和 docxtpl
库。其中,docxtpl
是基于 python-docx
的模板引擎,支持 Jinja2 模板语法,非常适合根据模板和数据生成报告。
1. 新建项目、引入依赖
pip install docxtpl
2. 创建word模板
3. 程序导出word文档
from datetime import datetimefrom docxtpl import DocxTemplate
import jsondef 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 datetimefrom docxtpl import DocxTemplate from docx2pdf import convert import jsondef 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}")# 如果需要导出PDFif export_pdf:pdf_path = output_path.replace('.docx', '.pdf')convert(output_path, pdf_path)print(f"PDF报告已生成:{pdf_path}")return pdf_pathreturn 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 subprocessdef 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}")# 导出 PDFif 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_pathexcept Exception as e:raise RuntimeError(f"执行异常, 转换PDF失败: {e}")return output_pathdef 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 会生成在指定目录,文件名相同,扩展名为 .pdfgenerated_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