使用 docxtpl 高效生成 Word 文档
docxtpl 是一个基于 python-docx 和 Jinja2 的 Python 库,它允许你在 Word 模板中使用 Jinja2 语法,然后动态填充数据生成 Word 文档。
1. 安装
pip install docxtpl
2. 基础用法
2.1 简单示例
创建模板文档 (template.docx):
在 Word 中创建模板,使用 {{ 变量名 }} 作为占位符
保存为 template.docx
Python 代码:
from docxtpl import DocxTemplate
# 加载模板
doc = DocxTemplate("template.docx")
# 准备上下文数据
context = {
'company_name': 'ABC 科技有限公司',
'date': '2023-12-01',
'client_name': '张先生',
'amount': '15000',
'project_name': '网站开发项目'
}
# 渲染文档
doc.render(context)
# 保存文档
doc.save("generated_document.docx")
print("文档已生成")
2.2 完整示例
from docxtpl import DocxTemplate
from datetime import datetime
def generate_contract():
"""
生成合同文档示例
"""
# 加载模板
doc = DocxTemplate("contract_template.docx")
# 准备数据
context = {
# 基本信息
'contract_number': 'HT202312001',
'sign_date': datetime.now().strftime('%Y年%m月%d日'),
'company_name': '创新科技股份有限公司',
'client_name': '卓越企业集团',
# 项目信息
'project_name': '智能管理系统开发',
'project_content': [
'需求分析与系统设计',
'前后端开发与实现',
'系统测试与优化',
'部署与培训',
'三个月维护期支持'
],
'project_duration': '2023年12月1日至2024年3月1日',
'total_amount': '¥150,000.00',
'payment_schedule': [
{'phase': '合同签订后', 'amount': '¥45,000.00', 'percentage': '30%'},
{'phase': '开发完成时', 'amount': '¥75,000.00', 'percentage': '50%'},
{'phase': '验收合格后', 'amount': '¥30,000.00', 'percentage': '20%'},
],
# 联系信息
'contact_person': '李经理',
'phone': '13800138000',
'email': 'contact@innovate-tech.com',
'address': '北京市海淀区科技园A座1001室'
}
# 渲染文档
doc.render(context)
# 保存
output_filename = f"合同_{context['contract_number']}.docx"
doc.save(output_filename)
return output_filename
if __name__ == "__main__":
result = generate_contract()
print(f"合同已生成: {result}")
3. 高级功能
3.1 处理复杂数据结构
from docxtpl import DocxTemplate
def generate_report_with_tables():
"""
生成包含表格和列表的报告
"""
doc = DocxTemplate("report_template.docx")
context = {
'report_title': '2023年第四季度销售报告',
'report_date': '2023-12-31',
# 表格数据
'sales_data': [
{
'region': '华北',
'q1': 1500000,
'q2': 1800000,
'q3': 2100000,
'q4': 2500000,
'total': 7900000
},
{
'region': '华东',
'q1': 2200000,
'q2': 2400000,
'q3': 2600000,
'q4': 3000000,
'total': 10200000
},
{
'region': '华南',
'q1': 1800000,
'q2': 2000000,
'q3': 2300000,
'q4': 2700000,
'total': 8800000
}
],
# 列表数据
'achievements': [
'完成年度销售目标的120%',
'成功开拓5个新市场',
'客户满意度提升至95%',
'团队规模扩大至50人'
],
# 带条件的变量
'performance': '优秀',
'next_year_target': '增长30%'
}
doc.render(context)
doc.save("季度销售报告.docx")
print("报告已生成")
# 在模板中使用表格循环
"""
模板示例:
季度销售报表
{% for item in sales_data %}
{{ item.region }}地区:
第一季度:{{ item.q1 }}
第二季度:{{ item.q2 }}
第三季度:{{ item.q3 }}
第四季度: {{ item.q4 }}
总计:{{ item.total }}
{% endfor %}
"""
3.2 使用条件语句
def generate_invoice_with_conditions():
"""
生成带有条件逻辑的发票
"""
doc = DocxTemplate("invoice_template.docx")
order_items = [
{'name': '笔记本电脑', 'quantity': 2, 'unit_price': 6500, 'discount': 500},
{'name': '显示器', 'quantity': 3, 'unit_price': 1500, 'discount': 0},
{'name': '键盘鼠标套装', 'quantity': 5, 'unit_price': 300, 'discount': 50},
]
# 计算总金额
total_amount = sum(item['quantity'] * item['unit_price'] for item in order_items)
total_discount = sum(item['discount'] for item in order_items)
amount_payable = total_amount - total_discount
context = {
'invoice_number': 'INV202312001',
'order_items': order_items,
'total_amount': total_amount,
'total_discount': total_discount,
'amount_payable': amount_payable,
# 条件判断
'has_discount': total_discount > 0,
'discount_note': f'已享受优惠 ¥{total_discount}' if total_discount > 0 else '无优惠',
# 金额大写转换
'amount_in_words': num_to_cn(amount_payable)
}
doc.render(context)
doc.save(f"发票_{context['invoice_number']}.docx")
def num_to_cn(num):
"""数字转中文大写"""
# 简化实现,实际使用时可以使用完整实现
return "人民币" + str(num) + "元整"
3.3 插入图片
from docxtpl import DocxTemplate, InlineImage
from docx.shared import Mm
def generate_certificate_with_image():
"""
生成带有图片的证书
"""
doc = DocxTemplate("certificate_template.docx")
context = {
'student_name': '张三',
'course_name': 'Python高级编程',
'completion_date': '2023年12月15日',
'score': '98',
# 插入图片
'logo': InlineImage(doc, 'company_logo.png', width=Mm(50)),
'signature': InlineImage(doc, 'signature.png', width=Mm(80)),
'stamp': InlineImage(doc, 'stamp.png', width=Mm(40))
}
doc.render(context)
doc.save("毕业证书.docx")
3.4 使用 RichText
from docxtpl import DocxTemplate, RichText
def generate_document_with_richtext():
"""
生成带有富文本格式的文档
"""
doc = DocxTemplate("richtext_template.docx")
# 创建富文本
rt = RichText()
rt.add('正常文本 ')
rt.add('加粗文本', bold=True)
rt.add(' ')
rt.add('红色文本', color='FF0000')
rt.add(' ')
rt.add('带下划线的文本', underline=True)
rt.add('\n')
rt.add('不同大小的文本', size=24)
context = {
'title': '富文本示例文档',
'richtext_content': rt,
'footer_note': '使用docxtpl生成'
}
doc.render(context)
doc.save("富文本示例.docx")
4. 模板设计技巧
4.1 Word 模板制作指南
变量格式:
{{ variable_name }}
循环语句:
{% for item in items %}
{{ item.name }}
{% endfor %}
条件语句:
{% if condition %}
显示的内容
{% endif %}
表格循环: 在表格行中使用循环
嵌套结构: 支持嵌套的循环和条件
4.2 常用模板标签
{# 注释 #}
{# 变量 #}
{{ title }}
{# 循环 #}
{% for item in list %}
- {{ item.name }}
{% endfor %}
{# 条件 #}
{% if score >= 90 %}
优秀
{% elif score >= 60 %}
合格
{% else %}
不合格
{% endif %}
{# 过滤器 #}
{{ date|date_format("%Y-%m-%d") }}
{{ text|truncate(100) }}
5. 性能优化技巧
from docxtpl import DocxTemplate
import time
from functools import lru_cache
class DocumentGenerator:
"""文档生成器类"""
def __init__(self, template_path):
self.template_path = template_path
self._template = None
@property
def template(self):
"""延迟加载模板"""
if self._template is None:
self._template = DocxTemplate(self.template_path)
return self._template
def generate_batch_documents(self, data_list):
"""
批量生成文档
"""
results = []
for i, data in enumerate(data_list):
try:
# 每次使用新的模板实例
doc = DocxTemplate(self.template_path)
doc.render(data)
filename = f"document_{i+1}.docx"
doc.save(filename)
results.append(filename)
# 内存清理
del doc
except Exception as e:
print(f"生成文档 {i+1} 时出错: {str(e)}")
return results
def generate_with_optimization(self, context, output_path):
"""
优化版的文档生成
"""
start_time = time.time()
# 预处理数据
processed_context = self._preprocess_context(context)
# 生成文档
doc = self.template
doc.render(processed_context)
doc.save(output_path)
end_time = time.time()
print(f"文档生成耗时: {end_time - start_time:.2f}秒")
return output_path
def _preprocess_context(self, context):
"""预处理上下文数据"""
# 这里可以添加数据预处理逻辑
return context
# 使用示例
if __name__ == "__main__":
generator = DocumentGenerator("template.docx")
# 准备批量数据
batch_data = [
{'name': f'用户{i}', 'id': i, 'date': '2023-12-01'}
for i in range(1, 101)
]
# 批量生成
results = generator.generate_batch_documents(batch_data)
print(f"成功生成 {len(results)} 个文档")
6. 错误处理
import logging
from docxtpl import DocxTemplate, DocxTemplateError
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def safe_document_generation(template_path, context, output_path):
"""
安全的文档生成函数
"""
try:
# 检查模板文件
if not os.path.exists(template_path):
raise FileNotFoundError(f"模板文件不存在: {template_path}")
# 加载模板
doc = DocxTemplate(template_path)
# 验证上下文数据
if not context or not isinstance(context, dict):
raise ValueError("上下文数据必须为非空字典")
# 渲染文档
doc.render(context)
# 保存文档
doc.save(output_path)
logger.info(f"文档已成功生成: {output_path}")
return True
except DocxTemplateError as e:
logger.error(f"模板处理错误: {str(e)}")
return False
except Exception as e:
logger.error(f"生成文档时发生错误: {str(e)}", exc_info=True)
return False
# 使用示例
context = {
'title': '测试文档',
'content': '这是一个测试'
}
success = safe_document_generation(
template_path="template.docx",
context=context,
output_path="output.docx"
)
if success:
print("文档生成成功")
else:
print("文档生成失败")
7. 实际应用示例
from docxtpl import DocxTemplate
import pandas as pd
from datetime import datetime
import os
class ReportGenerator:
"""报告生成器"""
def __init__(self):
self.templates_dir = "templates"
self.output_dir = "output"
self._ensure_directories()
def _ensure_directories(self):
"""确保目录存在"""
for directory in [self.templates_dir, self.output_dir]:
os.makedirs(directory, exist_ok=True)
def generate_from_excel(self, excel_path, template_name):
"""
从 Excel 数据生成报告
"""
# 读取 Excel 数据
df = pd.read_excel(excel_path)
# 加载模板
template_path = os.path.join(self.templates_dir, f"{template_name}.docx")
doc = DocxTemplate(template_path)
# 准备数据
reports = []
for _, row in df.iterrows():
context = {
'report_id': row['ID'],
'department': row['部门'],
'manager': row['负责人'],
'quarter': row['季度'],
'target': row['目标'],
'achievement': row['完成'],
'completion_rate': f"{(row['完成']/row['目标'])*100:.1f}%" if row['目标'] > 0 else "0%",
'analysis': row['分析说明'],
'next_plan': row['下季度计划'],
'generate_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
# 生成单个报告
doc = DocxTemplate(template_path) # 重新加载模板
doc.render(context)
output_filename = f"报告_{row['部门']}_Q{row['季度']}.docx"
output_path = os.path.join(self.output_dir, output_filename)
doc.save(output_path)
reports.append(output_path)
return reports
def generate_summary_report(self, data_list, template_name="summary_template"):
"""
生成汇总报告
"""
template_path = os.path.join(self.templates_dir, f"{template_name}.docx")
doc = DocxTemplate(template_path)
# 计算汇总数据
total_target = sum(item['target'] for item in data_list)
total_achievement = sum(item['achievement'] for item in data_list)
avg_rate = (total_achievement / total_target * 100) if total_target > 0 else 0
context = {
'report_title': '各部门季度业绩汇总报告',
'generate_date': datetime.now().strftime('%Y年%m月%d日'),
'departments': data_list,
'total_target': f"¥{total_target:,.2f}",
'total_achievement': f"¥{total_achievement:,.2f}",
'avg_completion_rate': f"{avg_rate:.1f}%",
'best_department': max(data_list, key=lambda x: x['achievement'])['name'] if data_list else "无",
'analysis': self._generate_analysis(data_list)
}
doc.render(context)
output_path = os.path.join(self.output_dir, "汇总报告.docx")
doc.save(output_path)
return output_path
def _generate_analysis(self, data):
"""生成分析报告"""
# 简化的分析逻辑
if not data:
return "暂无数据"
best = max(data, key=lambda x: x['achievement'])
worst = min(data, key=lambda x: x['achievement'])
return f"""
本季度共 {len(data)} 个部门参与考核。
表现最佳的部门是 {best['name']},完成业绩 {best['achievement']}。
需要重点关注 {worst['name']} 部门的业绩提升。
"""
# 使用示例
if __name__ == "__main__":
generator = ReportGenerator()
# 模拟数据
department_data = [
{'name': '销售部', 'target': 1000000, 'achievement': 1200000},
{'name': '技术部', 'target': 800000, 'achievement': 850000},
{'name': '市场部', 'target': 600000, 'achievement': 750000},
{'name': '行政部', 'target': 200000, 'achievement': 180000},
]
# 生成汇总报告
summary_report = generator.generate_summary_report(department_data)
print(f"汇总报告已生成: {summary_report}")
总结
docxtpl 的优势:
- 基于熟悉的 Jinja2 语法
- 支持复杂的逻辑控制
- 保持 Word 文档的格式
- 易于与现有系统集成
最佳实践:
- 合理设计模板结构
- 预处理数据,减少模板中的复杂逻辑
- 使用批量生成时注意内存管理
- 添加适当的错误处理和日志记录
适用场景:
- 合同、报告等模板化文档生成
- 批量生成相似结构的文档
- 需要保持专业格式的业务文档
这个方案特别适合需要大量生成格式统一的 Word 文档的业务场景,如合同、报告、证书等。