欢迎光临本地信息咨询网
详情描述
使用 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 文档的业务场景,如合同、报告、证书等。

相关帖子
为什么东北澡堂里总有人带搓澡巾,南方人却更爱淋浴间自己解决?
为什么东北澡堂里总有人带搓澡巾,南方人却更爱淋浴间自己解决?
如何检查和维护家中存放的应急物资,确保它们随时处于可用状态?
如何检查和维护家中存放的应急物资,确保它们随时处于可用状态?
未来社会的发展,是否会逐渐淡化户籍身份带来的各种差异?
未来社会的发展,是否会逐渐淡化户籍身份带来的各种差异?
马拉松赛道边的补给站,提供的饮料与食品通常包含哪些种类与成分?
马拉松赛道边的补给站,提供的饮料与食品通常包含哪些种类与成分?
2026年情绪消费成为新趋势,年轻人为何愿意为心情买单?
2026年情绪消费成为新趋势,年轻人为何愿意为心情买单?
自由职业者或灵活就业人员,是否可以为自己设立交通通讯补贴以降低税负?
自由职业者或灵活就业人员,是否可以为自己设立交通通讯补贴以降低税负?
2026年,社会舆论和年轻人对彩礼“限高”政策的普遍看法是什么?
2026年,社会舆论和年轻人对彩礼“限高”政策的普遍看法是什么?
计算经济补偿金时,工资基数是指应发工资还是实发工资?
计算经济补偿金时,工资基数是指应发工资还是实发工资?
到2026年,社保“第六险”的评估标准是否更加统一和规范了?
到2026年,社保“第六险”的评估标准是否更加统一和规范了?
贷款期间房产信息变更,讲解信息修改及二次抵押备案调整流程
贷款期间房产信息变更,讲解信息修改及二次抵押备案调整流程
从千兆宽带降级到百兆,运营商要求缴纳高额设备调测费是否合理?
从千兆宽带降级到百兆,运营商要求缴纳高额设备调测费是否合理?
2026年新的食品标识规定,是否让预制菜配料表更透明易懂?
2026年新的食品标识规定,是否让预制菜配料表更透明易懂?
智能汽车记录的行车轨迹信息是否属于个人隐私信息范畴
智能汽车记录的行车轨迹信息是否属于个人隐私信息范畴
如何区分是为了实际需求购物,还是为了短暂的情绪抚慰而进行消费?
如何区分是为了实际需求购物,还是为了短暂的情绪抚慰而进行消费?
2026年,企业的加班审批制度如果过于严格,是否会催生更多隐形加班?
2026年,企业的加班审批制度如果过于严格,是否会催生更多隐形加班?
企业内部非正式的社交活动,如何可能加深某些群体的“隐性”排斥感?
企业内部非正式的社交活动,如何可能加深某些群体的“隐性”排斥感?
对于有过敏性鼻炎的人,在特定季节应如何护理以减少鼻子出血风险?
对于有过敏性鼻炎的人,在特定季节应如何护理以减少鼻子出血风险?
小满节气“苦菜秀”的说法从何而来,这种植物背后有哪些有趣的故事与文化?
小满节气“苦菜秀”的说法从何而来,这种植物背后有哪些有趣的故事与文化?
对维修基金的使用情况有疑问,业主可以通过哪些合法途径进行监督?
对维修基金的使用情况有疑问,业主可以通过哪些合法途径进行监督?
2026年国际原油市场价格波动,主要受全球哪些非政治性供需因素影响?
2026年国际原油市场价格波动,主要受全球哪些非政治性供需因素影响?