Python-docx 实现 Word 文档批量生成与模板替换
2026/9/4 21:13:58 网站建设 项目流程

熟悉办公场景的技术人应该都有过这种体会:Word 文档本身不难写,难的是“按统一规范批量写”。一份合同改 50 个客户名称、一份周报把上周数据全部替换、20 份奖状要生成不同姓名,这些工作如果靠人工复制粘贴,不仅慢,而且很难保证每份文件的格式完全一致。哪怕是熟练使用 Word 的人,面对几百个文档时也会进入“体力劳动”状态。

Python 办公自动化真正解决的不是“帮你在 Word 里敲字”,而是把“文档生成逻辑”变成代码,用同样的规则批量产出格式稳定的文件。Word 文档处理也因此成为 Python 办公自动化里最值得优先掌握的技能之一。尤其是python-docx这个库,它不依赖本机是否安装 Microsoft Office,跨平台可用,适合服务端批处理,也非常适合写一次性脚本。

这篇文章会从一个实际开发者的视角来拆解 Word 文档自动化的核心知识:docx 的文件结构是什么、python-docx 的核心对象模型怎么理解、如何从零生成文档、如何批量替换模板文档中的占位符,以及最常见的坑和工程建议。读完你不仅能照着写出第一个可运行脚本,还能理解为什么很多“看起来很简单”的 Word 自动化问题,实际踩坑都在格式和段落细节上。

如果你刚学完 Python 基础语法,想找一个能立刻产生成果的练习方向,Word 自动化是很好的选择:代码量不大,但反馈非常直观,文件生成后打开就能看到效果。

1. 为什么 Python 办公自动化先拿 Word 开刀

很多人学 Python 办公自动化,第一个想到的往往是用 openpyxl 操作 Excel。Excel 数据有明确的单元格结构,批处理逻辑清晰:读行、写行、算汇总,这很好理解。但 Word 不一样,Word 文档是“流式排版”,信息和格式混在一起,操作起来比 Excel 更反直觉。

然而 Word 在办公流程中出现的频率并不低:通知公告、合同协议、调研报告、结课证书、员工入职资料、测试报告……这些文档的特点是:内容结构高度相似,只有少量字段不同。如果纯手工处理,最容易出问题的不是“打字速度”,而是格式不一致,比如不同人的报告中标题字号不同、表格边框丢失、页面边距不统一。

用 Python 处理 Word 的核心价值有三个:

第一,规则可复现。只要把“标题用几号字、段落怎么缩进、表格几行几列”写进代码,每次运行都是同一套规则,不再依赖操作者当时的细心程度。

第二,批量处理能力。一个人用 Ctrl+C、Ctrl+V 处理 100 份合同时,每份之间可能产生细微差异,但脚本不会。输入是同一份数据,输出就是同一套标准。

第三,图文混排与模板结合。Word 表格、图片、页眉页脚、分页符都能在代码中控制,这种能力让自动化不再局限于纯文本。

判断一个办公自动化任务是否值得用 Python 做,标准也很简单:如果这个任务需要重复做三次以上,每次只有少量字段不同,那就可以写脚本。尤其是 Word 文档,一旦模板固定下来,后续每次生成新版本,成本几乎为零。

2. 先搞清楚 docx 的文件本质,而不是直接开写

很多人第一次用 python-docx 时都会遇到一个奇怪的问题:为什么我明明指定了 .doc 文件,程序却报错?原因要从 Word 文件的格式讲起。

2.1 docx 不是一个“纯文本文件”

从 Word 2007 开始,默认的.docx文件本质上是一个 ZIP 压缩包,里面包含多个 XML 文件。用解压软件打开一个 docx,你会看到类似这样的结构:

word/ document.xml styles.xml numbering.xml media/ ... [Content_Types].xml _rels/

真正页面中的文字、段落结构、表格、图片引用关系,都记录在word/document.xml这个 XML 文件里。也就是说,Word 文档的底层是一套 XML 协议,阅读器负责把 XML 渲染成我们看到的排版页面。

理解了这一点,就明白为什么不能直接open("xxx.docx")然后读文本:你已经把一个 ZIP 包当成普通文本打开了,得到的是乱码。而 python-docx 做的事情,就是在 Python 层封装这套 XML 操作。你创建一段文本、设置一个字体,最终都会被翻译成对应的 XML 节点写入压缩包。

2.2 .doc 和 .docx 的差异要分清

.doc是 Word 2003 及更早版本的二进制格式,.docx是 Office Open XML 格式。python-docx 官方说明明确只支持.docx,不能读取旧版.doc文件。如果工作环境中还有.doc文件,需要先用 Word/WPS 另存为.docx,或者使用仅在 Windows 平台生效的 win32com 方案。但 win32com 依赖本机已安装 Office,不适合服务端跨平台场景。因此在写办公自动化项目时,最稳妥的做法是统一把源文件转成.docx再进入 Python 流程。

2.3 常见的 Word 处理方案对比

方案跨平台是否依赖 Office适用场景学习成本
python-docx创建标准 docx、修改段落、表格、样式
docxtpl基于模板做占位符批量填充
win32com否(仅 Windows)需要调用 Office 高级功能
手动解析 XML特殊定制或性能极致追求

python-docx 是多数办公自动化需求的最佳起点。docxtpl 适合纯模板替换,它底层依赖 python-docx,但引入了模板语法。对于初学者,我建议先把 python-docx 用熟练,再考虑模板引擎。

3. 环境准备与安装

本文示例以 Python 3 为基础。版本建议使用 Python 3.8 及以上,只要本地环境能运行 pip 即可,不同 Python 3 小版本之间的差异对本文示例没有影响。

推荐在项目目录中创建虚拟环境,避免依赖污染系统 Python:

mkdir word-automation-demo cd word-automation-demo python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate

激活虚拟环境后安装 python-docx:

pip install python-docx

安装完成后,可以先在 Python 交互环境中确认版本:

import docx print(docx.__version__)

这段代码如果能正常输出版本号,说明安装成功。注意导入包名是docx,而不是python-docx

文章后面还会用到docx.shareddocx.enum.text等子模块,它们随 python-docx 一起安装,不需要额外引入第三方包。

4. python-docx 的核心对象模型:先记住四个对象

python-docx 的对象模型并不复杂,但理解它的层级关系很重要,否则很容易写出“看起来对、实际无效”的代码。

4.1 四个核心对象

Document代表一份 Word 文档。你可以通过Document()创建空白文档,也可以Document("模板.docx")打开已有文件。

Paragraph代表文档中的一个段落。段落在 Word 中是最基本的块级单位,标题、正文、列表项本质上都是带不同样式的段落。

Run代表段落中具有相同格式的一段文字。同一个段落可能包含多个 Run,这是 Word 自动化和 Excel 自动化最大的思维差异点:Excel 单元格是纯数据,而 Word 段落里的文字是“分段”的,格式变化可能打断文字内容。

Table代表文档中的表格。表格是由行、列、单元格组成的,通过table.rowstable.columnscell.text访问和修改。

4.2 为什么 Run 是频繁踩坑的重点

举个例子,一个段落内容是“本项目名称为 Python 办公自动化”,如果用户手工选中“Python 办公自动化”改成红色字体,Word 内部会把这一段至少拆成三个 Run:

  • Run1:本项目名称为
  • Run2:Python 办公自动化(红色)
  • Run3:

如果只是读取整段文字,paragraph.text会把所有 Run 拼接起来,非常方便。但如果你要做精确替换,比如把“Python 办公自动化”替换成“Java 自动办公”,直接遍历段落文本并不能定位到具体 Run,因为你不知道要替换的内容跨越了哪几个 Run。

后面第 6 节的批量替换示例,会专门处理这个问题。

4.3 python-docx 的常规流程

使用 python-docx 处理一份文档,通常遵循下面几条路径:

  • 从零创建:Document()add_heading()add_paragraph()add_table()save()
  • 读取修改:Document("已有.docx")→ 遍历段落/表格 → 修改 →save()
  • 模板填充:准备一份含占位符的 docx → 打开 → 按规则替换 →save()到新文件

5. 实战任务一:从零生成一份带标题、段落、表格的 Word 报告

先跑通最小闭环。下面代码会创建一份 Word 报告,包括标题、说明文字、无序列表示例和一张 3 行 3 列的表格。

# 文件路径:generate_report.py from docx import Document from docx.shared import Pt from docx.enum.text import WD_ALIGN_PARAGRAPH doc = Document() # 添加标题 doc.add_heading('Python 自动生成报告', level=0) # 添加说明段落 p = doc.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.CENTER run = p.add_run('报告生成时间:2025年1月') run.font.size = Pt(10) doc.add_paragraph('这是一段由 python-docx 自动写入的正文。' '办公自动化的核心目标,是让格式统一的文档可以批量产出。') # 无序列表 doc.add_heading('检查项', level=1) doc.add_paragraph('文件可正常打开', style='List Bullet') doc.add_paragraph('标题层级正确', style='List Bullet') doc.add_paragraph('关键信息无遗漏', style='List Bullet') # 表格 doc.add_heading('结果汇总', level=1) table = doc.add_table(rows=3, cols=3) table.style = doc.styles['Table Grid'] headers = ['项目', '负责人', '状态'] data = [ ['需求梳理', '张三', '已完成'], ['代码开发', '李四', '进行中'], ] # 填充表头 for col_idx, header in enumerate(headers): table.rows[0].cells[col_idx].text = header # 填充数据 for row_idx, row_data in enumerate(data, start=1): for col_idx, cell_text in enumerate(row_data): table.rows[row_idx].cells[col_idx].text = cell_text # 保存 doc.save('report_demo.docx') print('文档已生成:report_demo.docx')

运行方式:

python generate_report.py

运行后,会在当前目录生成一个report_demo.docx。打开后检查三点:

  • 标题是否使用了“标题 0”样式的醒目效果。
  • 三个无序列表项是否带圆点符号。
  • 表格是否有边框,表头和两行数据是否正确。

需要说明的是,style='List Bullet'style='Table Grid'都是 python-docx 默认模板中的内置样式。如果你打开的是自己的企业模板,模板里不一定包含这两个样式名,此时更推荐用代码设置paragraph_format的缩进和表格边框,但这部分超出本次基础示例范围。

6. 实战任务二:批量填充模板中的占位符

第二个常见需求是:在 Word 中准备一份“模板”,正文位置留下【姓名】【部门】【考核结果】这样的占位符,然后通过 Python 批量生成多个最终文档。

6.1 为什么不建议直接用整段 replace 做替换

在做模板替换时,很多人的直接想法是遍历每个段落,判断paragraph.text中有没有占位符,然后用replace()处理整个段落文本。这种做法有一个隐患:paragraph.text是只读拼接结果,你无法直接给它赋值;如果强行把新文本写回第一个 Run,其他 Run 仍保留旧文本,最终文档会重复出现旧内容。

更安全的做法是:先拼接段落内所有 Run 的文本,如果段落中存在占位符,就生成替换后的完整文本,然后把完整文本写入第一个 Run,并清空其余 Run。

# 文件路径:template_utils.py from docx import Document def replace_in_paragraph(paragraph, data: dict): """在段落中替换占位符,data 的 key 是占位符,value 是真实内容。""" full_text = ''.join(run.text for run in paragraph.runs) if not full_text: return new_text = full_text for old, new in data.items(): if old in new_text: new_text = new_text.replace(old, str(new)) # 没有变化时,不要动原段落,避免破坏格式 if new_text == full_text: return # 把替换完成后的文本统一放进第一个 Run paragraph.runs[0].text = new_text for run in paragraph.runs[1:]: run.text = '' def fill_template(template_path: str, data: dict, output_path: str): """打开模板,替换正文和表格中的占位符,然后另存为新文件。""" doc = Document(template_path) # 替换普通段落 for para in doc.paragraphs: replace_in_paragraph(para, data) # 替换表格单元格里的段落 for table in doc.tables: for row in table.rows: for cell in row.cells: for para in cell.paragraphs: replace_in_paragraph(para, data) doc.save(output_path)

6.2 准备模板

你也可以用 Word 手工创建一个模板文件,命名为employee_template.docx。建议内容:

标题:员工考核通知

正文:

【姓名】同志: 经部门评定,您所在部门为【部门】。 本次考核结果:【考核结果】。 请于 3 个工作日内确认以上信息。

表格可以安排两列,用来放考核项和得分,这里面也可以包含【得分】占位符。建议在正式跑脚本前,先准备一个小体积模板测试,不要把复杂的公司正式文档直接拿来测试。

6.3 批量生成脚本

# 文件路径:batch_generate.py from template_utils import fill_template users = [ {'姓名': '张三', '部门': '研发部', '考核结果': '优秀'}, {'姓名': '李四', '部门': '产品部', '考核结果': '良好'}, {'姓名': '王五', '部门': '运营部', '考核结果': '合格'}, ] for user in users: data = { '【姓名】': user['姓名'], '【部门】': user['部门'], '【考核结果】': user['考核结果'], } output_file = f"考核通知_{user['姓名']}.docx" fill_template('employee_template.docx', data, output_file) print(f'已生成:{output_file}')

运行后,目录下会出现三份独立的 Word 文档:

考核通知_张三.docx 考核通知_李四.docx 考核通知_王五.docx

这个替换函数已经考虑了表格场景,所以即使占位符位于表格内,也能被扫描到。这里的重点不是代码技巧多高深,而是用“拼接全文 → 统一替换 → 写回首个 Run”的思路规避了 Word 将文本拆分为多个 Run 的问题。

不过也要知道这个方案的边界:它会丢失后几个 Run 原本的局部格式。如果模板中需要“占位符文字是红色但真实内容是黑色”这类特殊效果,不应该用这种整体重写方案,而应该进一步精确到 Run 级别或者使用模板引擎。

6.4 对批量生成任务的工程建议

在企业场景中,批量文档最容易出的问题不是代码报错,而是生成后没有检查:用户名称是否替换干净、日期是否过期、表格中是否存在遗漏的占位符。因此,我强烈建议在批量生成脚本后面加一段“校验逻辑”:

from docx import Document def check_placeholder(output_path, keys): doc = Document(output_path) all_text = [] for para in doc.paragraphs: all_text.append(para.text) for table in doc.tables: for row in table.rows: for cell in row.cells: all_text.append(cell.text) content = '\n'.join(all_text) for key in keys: if key in content: raise ValueError(f'{output_path} 中仍然存在占位符:{key}')

这样做能防止“看起来成功、实际漏数据”的情况。

7. 实战任务三:读取已有 Word 文档并提取关键内容

除了生成文档,办公自动化里另一个高频需求是「读取一批 Word 文档,生成摘要 Excel」或「从大量 docx 中搜索指定关键词」。python-docx 对读取的支持同样很直接。

下面代码会打开report_demo.docx,遍历所有段落和表格,输出文档结构:

# 文件路径:read_docx.py from docx import Document doc = Document('report_demo.docx') print('=== 段落内容 ===') for i, para in enumerate(doc.paragraphs): if para.text.strip(): print(f'第 {i} 段: {para.text}') print('\n=== 表格内容 ===') for t_idx, table in enumerate(doc.tables): print(f'表格 {t_idx + 1}:') for row in table.rows: cells = [cell.text for cell in row.cells] print(' | '.join(cells))

运行后,控制台会输出类似下面的内容:

=== 段落内容 === 第 0 段: Python 自动生成报告 第 1 段: 报告生成时间:2025年1月 第 2 段: 这是一段由 python-docx 自动写入的正文。办公自动化的核心目标...... ... === 表格内容 === 表格 1: 项目 | 负责人 | 状态 需求梳理 | 张三 | 已完成 代码开发 | 李四 | 进行中

这种读取能力非常适合做文档巡检:比如批量读取某个目录下的十余份实验报告 docx,检查是否包含“结论”“风险”字段,把检查结果输出成更易读的清单。

需要提醒的是,读取 Word 中的表格时,row.cells的长度不一定等于列数。因为 Word 表格存在“合并单元格”时,一个 cell 可能被多个 grid 列占据,直接遍历可能得到重复对象。如果项目里出现了这个问题,不要死磕当前代码,优先通过调整源文档的表格结构来简化。

8. 常见问题与排查思路

很多 Word 自动化脚本运行失败,问题都出在“文件格式”“模板样式”“Run 拆分”这三个大方向上。下面汇总了新手最常遇到的几类问题。

问题现象可能原因排查方式解决方案
打开文件报错 Package not found传入的是 .doc 旧格式而不是 .docx检查文件扩展名,用文件类型识别确认先把 .doc 另存为 .docx
PermissionError: [Errno 13]目标 Word 文件正被 Office/WPS 占用关闭正在预览的文档后再运行脚本保存前先确认文件未被占用
替换没生效,原文字仍在文档中Word 把文本拆到多个 Run,函数只处理了部分 Run遍历段落打印每个 run.text,观察拆分情况使用“拼接全文 → 写回首个 Run”方案
中文字体设置无效只设置了 run.font.name,没有同时设置 eastAsia 字体查看 XML 中字体名称使用 qn('w:eastAsia') 设置中文字体
表格没有边框使用的样式在当前模板中不存在查看 doc.styles 列表选用模板内置的 table style,或自行设置边框
无论怎么改,模板仍显示占位符占位符可能在文本框、页眉、页脚中当前脚本只遍历段落和表格,没有覆盖文本框需要额外处理文本框或改为在正文编辑

8.1 中文字体设置的标准写法

如果只想让标题或某些 Run 显示为“黑体”,除了设置run.font.name,还需要通过 XML 设置中文字体:

from docx.oxml.ns import qn def set_run_font(run, font_name_cn, font_size_pt): run.font.name = font_name_cn run._element.rPr.rFonts.set(qn('w:eastAsia'), font_name_cn) run.font.size = Pt(font_size_pt)

这里的本质原因在于 Word 内部区分 ASCII 字体和东亚字体,只有.font.name会改变西文字体,中文字体必须通过w:eastAsia属性单独声明。

8.2 不要直接用“另存为”覆盖原始重要文件

在修改正式文档前,尽量先把脚本输出保存成新文件名,添加时间戳或_new后缀。确认最终效果没问题后,再决定是否归档旧文件。任何时候都不要在生产环境对没有备份的重要文档直接执行不可逆的批量替换逻辑。

任何办公自动化脚本运行前,都应确保:一来只操作你拥有合法处理权限的文档,二来涉及个人、客户信息的批量任务先使用脱敏测试数据验证。Python 脚本操作 Word 文件不需要跳过高安全限制,它只是在做常规文件读写。

9. 最佳实践与工程化建议

从“能生成一个 docx”到“能在正式环境稳定运行”,中间还隔着不少工程习惯。下面几条是处理 Word 自动化任务时最值得坚持的经验。

9.1 把模板和业务逻辑分离

不要把所有内容都写死在 Python 代码里。一份正式报告的标题样式、页边距、公司 Logo 位置,最适合放在模板 docx 中,由人工维护;代码只负责打开模板、填充数据、保存新文件。这样即使 Word 排版调整,也不需要改 Python 代码,只需要重新维护模板。

如果模板中存在大量固定排版需求,推荐结合docxtpl这类模板引擎。它的使用模式是:在 Word 模板中写{{ name }}之类的标记,Python 端传入一个字典,然后一次性渲染。这种方式比手工遍历 Run 更稳定,适合模板结构较复杂的场景。

9.2 占位符命名要有规则

【姓名】【部门】{{ name }}都可以,但一定要让占位符可被搜索。推荐统一使用不会在正文里自然出现的符号,例如{{name}},并保证模板内没有遗漏的原始占位符。命名规则建议记录到项目的 README 中,避免同事拿到模板后不知道该填什么。

9.3 对输出文件做自动化校验

数据缺失是批量生成任务最隐蔽的问题。建议在批量生成之后增加一道校验脚本:重新打开生成好的 docx,检查是否存在预期关键词或占位符残留。校验步骤不一定很复杂,但能在批量生成 100 份文件时防止人工一份份打开检查。

9.4 控制依赖版本

办公自动化脚本通常不是一次性小程序,它们常被加入定时任务或调度平台。遇到这类情况,建议把依赖写入 requirements.txt:

pip freeze > requirements.txt

下次部署时执行:

pip install -r requirements.txt

这能避免因为 python-docx 版本升级导致 API 行为变化,保证脚本长期稳定。

9.5 在没有 Office 的服务器上测试

python-docx 不依赖 Office,正好适合部署在 Linux 服务器上生成 Word。如果你的环境是 Windows,也要注意别让自动生成的文件被 Office 软件长期占用,否则后续脚本保存文件时会遇到 PermissionError。

正确的流程是:用 Word/WPS 编辑模板 → 关闭文档 → 运行 Python 脚本 → 生成结果 → 再人工打开检查。不要在脚本运行期间用 Word 打开同一个输出文件。

10. 总结与后续学习方向

从这篇文章的示例可以看出,Python 处理 Word 文档并不神秘。核心是先理解 docx 的底层结构,然后掌握 Document、Paragraph、Run、Table 这个对象模型。你只需要按“创建或打开模板 → 操作内容 → 保存”这套流程写代码,就能完成大多数办公自动化任务。

本文提到的三个任务——从零生成报告、批量替换模板占位符、读取已有文档内容,是 Word 办公自动化中最常见的三种基本模式。把它们组合起来,可以覆盖合同生成、证书批量打印、报告汇总、数据转 Word 表格等大量实际场景。

如果你想继续深入,下一步方向有三个:

  • 学习 docxtpl,掌握基于模板的更稳定文本填充方案。
  • 研究表格的单元格合并与边框控制,处理复杂格式。
  • 结合 openpyxl 读写 Excel 数据,建立“Excel 数据 → Word 文档”的完整生产线。

遇到新需求先别急着写代码,打开一个 Word 文档,看看它的结构是否工整,存成 docx,再设计你的字段和循环逻辑。办公自动化的收益,往往不是单一脚本写得多漂亮,而是它能在未来每一次相同任务时替你省下重复劳动。建议先从一个最小模板试验,保存为单独文件,跑通后再部署到真实批量场景中。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询