欢迎光临本地信息咨询网
详情描述
使用 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年的市场环境,独立开发者应聚焦解决用户的哪些痛点?
别墅抵押贷款LPR怎么影响利率?LPR调整与月供变化关系解读
别墅抵押贷款LPR怎么影响利率?LPR调整与月供变化关系解读
如何通过角色扮演类玩具,有效培养孩子的同理心与情绪管理能力?
如何通过角色扮演类玩具,有效培养孩子的同理心与情绪管理能力?
过桥垫资让与担保效力?名为买卖实为借贷
过桥垫资让与担保效力?名为买卖实为借贷
别墅抵押贷款可以抵押给典当行吗?典当行别墅抵押贷款利弊
别墅抵押贷款可以抵押给典当行吗?典当行别墅抵押贷款利弊
公司经营贷款集团授信流程,企业集团统一授信
公司经营贷款集团授信流程,企业集团统一授信