python-docx 修改文字需调用 document.save() 才保存,直接改 paragraph.text 会丢失格式,应遍历 run 替换;水印属页眉 Shape,python-docx 无法读取,Windows 可用 win32com,跨平台可模拟 textbox 水印。

用 python-docx 替换正文文字时,为什么改了却没保存成功?
python-docx 默认不自动保存修改,所有 paragraph.text 或 run.text 的赋值只是内存操作。必须显式调用 document.save() 才写入文件——但更关键的是:直接改 paragraph.text 会清空原有格式(加粗、颜色、字体等),实际生产中几乎不可用。
正确做法是遍历每个 run,在保持格式的前提下替换文本内容:
- 逐段(
paragraph)→ 逐运行(run)→ 检查run.text是否含目标字符串 - 用
run.text.replace(old, new)赋值,而非整体重设paragraph.text - 若需跨 run 匹配(如“hello”被拆在两个 run 中),得先合并相邻 run 再处理——这很罕见,一般先跳过
- 替换后务必调用
doc.save(output_path),且路径不能和原文件相同(Windows 下会报错 PermissionError)
水印不是普通文本,python-docx 根本读不到怎么办?
Word 中的水印本质是页眉里的 Shape 对象(图片或艺术字),而 python-docx 不支持读取或修改页眉/页脚中的图形元素——这是它的硬性限制,不是你代码写错了。
可行路径只有两条:
- 用
win32com.client(仅 Windows)调用 Word COM 接口,真实打开文档,操作页眉中的Shapes,可删水印、插新水印 - 手动解压 .docx(它本质是 zip),进
word/header.xml或word/_rels/header.xml.rels找水印相关 XML 片段,正则替换再重打包——风险高,易破坏文档结构,不推荐 - 如果水印是插入的图片,且你只要“批量删除”,可用
zipfile直接删掉word/media/xxx.png并清理对应 XML 引用,但需同步修 header.xml 和 document.xml,实操门槛高
处理几百个文档时,为什么程序卡死或内存爆掉?
python-docx 加载一个大文档(尤其带图、表格多的)可能吃掉 100MB+ 内存;循环打开-修改-保存 500 个文档,不释放对象会迅速耗尽内存。
必须做三件事:
- 每次处理完一个文档后,显式删掉变量引用:
del doc,再调用gc.collect()(虽不总有效,但有胜于无) - 避免用
glob.glob("*.docx")一次性加载全部路径到内存,改用pathlib.Path(".").rglob("*.docx")迭代器流式读取 - 对超大文档(>10MB),优先考虑用
win32com的Visible=False后台模式,它由 Word 进程管理内存,比 python-docx 更稳 - 加异常捕获:某些损坏文档会让
Document()直接抛KeyError或XMLSyntaxError,需跳过并记录日志
跨平台批量处理水印,有没有更稳妥的替代方案?
没有真正跨平台又免费的“全自动水印替换”方案。Linux/macOS 上无法用 win32com,而 libreoffice headless 模式虽能导出/转存,但不提供 API 修改水印——它把水印当页面背景渲染,不在文档逻辑结构里。
现实中的折中做法:
- Windows 环境:坚持用
win32com,封装成函数,传入文档路径和新水印文本,调用doc.Sections(1).Headers(1).Shapes遍历删旧、插新 - 非 Windows 环境:放弃“改水印”,改为“统一加新水印”——用 python-docx 新建一个带水印的空白模板,再把原文档正文复制进去,覆盖原内容(保留原格式难度大,但至少水印可控)
- 若水印只是半透明文字(非图片),可退一步,在每页末尾插入一个固定位置的
textbox,设置旋转、透明度模拟水印效果——python-docx支持add_textbox(),虽不完美,但跨平台可用
水印操作永远比文字替换复杂,因为 Word 把它藏在布局层而不是内容层。别指望一个库全搞定,得按环境选工具链,且留足调试时间——尤其是 COM 接口在不同 Office 版本间的行为差异,经常要单独适配。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











