ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Word自动化实战:分清doc与docx,搞定读取、批量生成与格式转换

Word自动化实战:分清doc与docx,搞定读取、批量生成与格式转换 做 Python 办公自动化的时候Excel 和 PDF 往往是先被玩熟的方向但一遇到 Word 文档很多人就会卡住。不是 Python 不强大而是 Word 文档本身有两套完全不同的体系新版的 .docx 和旧版的 .doc。如果格式都没分清就容易出现本地能跑、换台电脑就报错或者用 python-docx 打开 .doc 直接抛异常的问题。这篇文章专门把 Word 文档处理这条线拆开讲先讲格式和工具选型再讲读取、批量生成、格式转换最后给出一套实际排查报错时的顺序。适合刚入门 Python、正在做办公自动化的学习者也适合需要在服务器上批量处理 Word 文件的技术人员。1. 先分清 Word 文档的真实格式是 .doc 还是 .docx1.1 为什么 .doc 和 .docx 不能混用工具先看一个最常见的坑拿到一个文件后缀写的是.doc直接丢给 python-docx 处理结果报PackageNotFoundError。这不是 Python 代码写错了而是 python-docx 根本不支持旧版.doc。.docx本质是一个基于 Office Open XML 的压缩包里面主要文件是word/document.xml所以它能被很多纯 Python 库解析。.doc则是旧版 Word 使用的二进制格式底层是一个 OLE2 复合文档容器解析逻辑完全不同。后缀可以直接改名但文件内部结构不会变。把.doc重命名为.docx只会让程序打开时更困惑而不是更兼容。判断文件真实格式最稳妥的办法不是看后缀而是看文件头。.docx文件开头通常是PK也就是 zip 格式标志.doc文件开头通常是D0 CF 11 E0 A1 B1 1A E1也就是 OLE2 复合文档标志。可以用一段简单代码判断from pathlib import Path def guess_word_format(path): with open(path, rb) as f: header f.read(8) if header[:2] bPK: return docx if header[:8] b\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1: return doc return unknown print(guess_word_format(测试文档.doc)) print(guess_word_format(测试文档.docx))这个检查看起来简单能在批处理前挡掉大量无效任务。如果输入目录里有几千个文件不可能一个一个打开验脚本先批量扫一遍格式再决定后续处理流程是最实用的习惯。1.2 不同系统下的工具选型我判断一个 Word 自动化项目该用什么方案通常会按“文件格式 操作系统 最终目标”三件事来选。方案能处理格式系统要求适合场景注意点python-docx.docx跨平台读取和生成新文档、修改段落、表格、样式不支持 .docpywin32 本机 Microsoft Word.doc、.docxWindows 已安装 Word复杂格式处理、调用 Word 转 PDF、兼容旧版 .doc不能跨平台依赖 OfficeLibreOffice 命令行.doc、.docx、odt 等多格式跨平台服务器批量转换格式复杂排版可能与 Word 有差异antiword / textract 等.doc 文本提取跨平台只提取纯文本格式保留能力有限如果只处理.docx优先学 python-docx。它不像 win32com 那样需要外部 Office也不依赖 Windows 系统部署简单。如果一定要处理.doc而且身边有 Windows Word用 pywin32 最省事。如果既不是 Windows 环境又装不了 Word那就先用 LibreOffice 把.doc转成.docx再用 python-docx 继续操作。这里要提醒一句不要指望一个工具吃遍所有场景。python-docx 适合结构化处理Win32COM 适合“调用 Word 本身的能力”LibreOffice 适合格式转换。选错了工具后面所有步骤都会难受。2. 环境准备装库、装 Office、准备 LibreOffice2.1 Python 环境与依赖库安装开始之前先把 Python 环境确认好。我一般会新建一个虚拟环境避免多个项目依赖互相冲突。这一步不是必须但如果这台机器上同时跑好几个 Python 项目最好还是分开。mkdir word_auto cd word_auto python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate激活虚拟环境后安装两个常用库pip install python-docx pywin32如果你的机器上已经装过也可以顺手升级一下但没必要追求最新版本。把这个项目的依赖记录下来方便以后复现。验证安装是否成功python -c from docx import Document; print(python-docx ok)在 Windows 上可以再验证 pywin32python -c import win32com.client; print(pywin32 ok)pywin32 不是跨平台库Linux 上安装后也不能调用 Word COM。如果你主要在 Linux 服务器上跑只需要 python-docx 和一个可用的 LibreOffice。很多初学者容易忽略虚拟环境和解释器选择。如果在 VS Code 里写代码建议先安装 Python 扩展然后点击右下角解释器选到刚才创建的venv。否则终端里明明装好了库编辑器运行还是提示ModuleNotFoundError这种问题基本都是解释器选错导致的。2.2 Windows、Linux 的落地差异Windows 下用 pywin32 处理 Word依赖的是“已安装的 Microsoft Office”。我建议至少保证本机能手动打开 Word并且不是精简版。有些精简版只保留了基础编辑功能COM 组件注册不完整脚本里调用Word.Application就会报错。Linux 环境没有 Word但有 LibreOffice。安装后可以通过 headless 模式转换文档。以 Ubuntu/Debian 为例sudo apt install libreoffice这句话一看就像老生常谈但实际坑很多。LibreOffice 安装包很大在服务器上安装前先确认磁盘空间如果只是转换 Word 文档不需要装全套组件但最小化安装有时反而缺失转换模块所以我一般还是直接装完整版。安装完成后先手动执行一次最简单的转换确认基础环境能跑通再放进代码里批量处理。另外路径问题很容易被忽略。Windows 的C:\Users\张三\文档包含中文、空格、反斜杠直接当字符串传给 Word COM 时偶尔会解析出问题。我的经验是脚本里全部用 pathlib 生成绝对路径再转成字符串传给底层接口不要手动拼接路径。Linux 服务器上文件名也可能包含中文统一使用 UTF-8 编码能省掉很多麻烦。3. 从读取开始把 Word 里的段落和表格变成 Python 对象3.1 用 python-docx 读取 .docx 的基本写法读取 Word 并不是把文件“读”出来就行而是要清楚自己想拿什么。是只要段落文字还是也要表格是否关心标题级别这会影响代码写法。先看最基础的段落读取from docx import Document doc Document(合同模板.docx) for idx, para in enumerate(doc.paragraphs): style_name para.style.name if para.style else None print(idx, style_name, para.text)这里把段落样式也打印出来原因是 Word 的“标题 1”“标题 2”等样式在后续做目录、文档切片、结构化处理时非常关键。如果只打印para.text你会丢掉段落属性后面想按章节切分文档时就只能自己猜层级。读取表格也很常用for t_idx, table in enumerate(doc.tables): print(f表格 {t_idx}) for row in table.rows: cells [cell.text.strip() for cell in row.cells] print( | .join(cells))注意一个关键点python-docx 的doc.paragraphs只会返回文档正文中的段落不会包含表格内部的段落。如果一个文档里既有正文又有表格你单独遍历doc.paragraphs会漏掉表格里的文字。想要完整提取就需要分别处理正文和表格或者自己封装一个遍历逻辑把文档对象里的段落、表格按顺序一起遍历出来。3.2 老式 .doc 文件怎么读处理.doc文本提取最直观的方案是在 Windows 上用 win32com 启动 Wordimport os import win32com.client word win32com.client.Dispatch(Word.Application) word.Visible False word.DisplayAlerts 0 try: doc word.Documents.Open(os.path.abspath(老文档.doc)) count doc.Paragraphs.Count for i in range(1, count 1): print(doc.Paragraphs(i).Range.Text) doc.Close(False) finally: word.Quit()Word COM 的集合索引从 1 开始这里需要注意别用 Python 习惯从 0 开始遍历否则很容易跳过第一段或者到最后越界。这段代码只能读取段落如果文档里包含文本框、页眉页脚、批注、复杂排版单靠这种遍历方式拿到的内容可能不完整。更稳的做法是先用 Word COM 把.doc另存为.docx再交给 python-docx 做后续处理doc.SaveAs2(os.path.abspath(转换后.docx), FileFormat16)16是 Word 内部的wdFormatXMLDocument保存为.docx。转换完成后doc.Close(False)再重新用 python-docx 打开新文件。这样处理起来统一后续的批量替换、样式修改都有更顺手的 API。如果你在 Linux 服务器上没有 Word可以先用 LibreOffice 把.doc转成.docxsoffice --headless --convert-to docx --outdir ./output 老文档.doc转换完成后再加载 docx。我的习惯是能转格式就先转不要硬啃旧格式解析。3.3 读取结果的验证标准读取文档后怎么判断抓取结果是否完整我一般会做三件事打印文本的行数和字符数先和 Word 原文件大致对比。随机抽 3 到 5 个关键段落查看内容是否完整、是否乱码。如果文档里有表格确认表格行数和列数没有丢失。很多长文档目录和正文之间的页码、分隔符并不影响实际文本内容。如果你要做的是“文档切片预处理”比如把 Word 内容拆成多个分块给知识库使用最好是按“标题 1、标题 2”的层级切分而不是单纯按字符数硬切。按字符硬切很容易把一段话切成两半导致语义断裂。用样式名判断层级再用标题文本作为切片边界整体效果会好很多。4. 批量生成文档模板、占位符和文件命名4.1 模板设计要点批量生成 Word 是办公自动化里最常见也最有价值的场景。和直接用代码从零创建一份漂亮文档相比用 Word 模板修改要快得多也更容易让非技术同事理解。模板里占位符要设计得足够“独特”。我不推荐用姓名、日期这样太普通的词因为 Word 正文里可能到处都是。更建议用{{姓名}}、{{日期}}这种带边界的写法这样替换精准不容易误伤。数据来源可以是 Excel、CSV、JSON甚至是数据库。为了演示我用一个简单的记录列表records [ {no: 001, name: 张三, date: 2025-05-01, amount: 1000.00}, {no: 002, name: 李四, date: 2025-05-02, amount: 2000.00}, ]如果数据量比较大建议用 pandas 读取 Excel 或 CSV再做数据清洗。先处理掉空值、非法字符再进入文档生成流程。4.2 替换占位符时最容易翻车的地方替换 Word 占位符很多新手会这样写for para in doc.paragraphs: if {{姓名}} in para.text: para.text para.text.replace({{姓名}}, 张三)但paragraph.text是只读属性直接赋值会报错。更常见的写法是遍历runs逐个替换for para in doc.paragraphs: for run in para.runs: if {{姓名}} in run.text: run.text run.text.replace({{姓名}}, 张三)这段代码在简单情况下能跑通但有一个很隐蔽的问题Word 会把一个段落内容拆成多个 run。你明明在 Word 里输入的是{{姓名}}实际保存后可能被拆成{{、姓名、}}三个 run。这时用if {{姓名}} in run.text去判断每一个 run 都不包含完整占位符替换就会失效。所以我一般会写一个更通用的函数先拼出段落完整文本如果里面有目标占位符就把所有 run 清空再把替换后的文本写回第一个 run。这样做会牺牲段落内的复杂格式但很适合占位符本段落这种场景。def replace_in_paragraph(paragraph, old, new): full_text paragraph.text if old not in full_text: return new_text full_text.replace(old, new) for run in paragraph.runs: run.text if paragraph.runs: paragraph.runs[0].text new_text如果是表格里的占位符要注意遍历所有单元格里的段落for table in doc.tables: for row in table.rows: for cell in row.cells: for para in cell.paragraphs: replace_in_paragraph(para, {{日期}}, date_text)如果你还要替换页眉页脚里的文本需要额外遍历每个 section 的 header。4.3 批量执行先小样本再全量批量生成最忌讳的是一上来就处理 500 份结果模板改错了500 份全错。更稳妥的顺序是先取 2 到 3 条数据试运行检查生成的文档后再跑全量。一个简单的批量生成脚本结构如下from pathlib import Path from docx import Document def render_document(record): template_path Path(templates/合同模板.docx) output_dir Path(output) output_dir.mkdir(exist_okTrue) doc Document(str(template_path)) for para in doc.paragraphs: replace_in_paragraph(para, {{编号}}, record[no]) replace_in_paragraph(para, {{姓名}}, record[name]) replace_in_paragraph(para, {{日期}}, record[date]) replace_in_paragraph(para, {{金额}}, record[amount]) out_name f{record[no]}_{record[name]}.docx out_path output_dir / out_name doc.save(str(out_path)) # 先用一条数据测试 render_document(records[0]) # 确认无误后再跑全量 # for record in records: # render_document(record)文件命名也要提前规划。我用编号_姓名.docx的结构容易辨认也方便后续按编号索引。注意文件名里不能包含\/:*?|这些字符数据和输入源不可靠时需要写一个清理函数避免保存时报错。这里再说一个容易踩的坑如果用 win32com 批量操作 Word尽量顺序执行不要一上来开很多线程。Word COM 本身适合单线程连续调用强行并发容易出现“进程异常”“文档未释放”等问题。python-docx 相对安全一些但Word文档处理通常不是性能瓶颈没有必要为了一点点速度引入复杂的并发模型。先把一次任务调稳再考虑优化。5. 文档转换Word 转 PDF、doc 转 docx、Markdown 转 Word5.1 Windows 下调用 Word 自带导出能力Word 转 PDF 是另一个高频需求。windows 环境里最接近“所见即所得”的方案是用 win32com 调用 Word 自带的导出能力import os import win32com.client word win32com.client.Dispatch(Word.Application) word.Visible False try: doc word.Documents.Open(os.path.abspath(合同模板.docx)) pdf_path os.path.abspath(合同模板.pdf) doc.ExportAsFixedFormat(pdf_path, 17) # 17 wdFormatPDF doc.Close(False) finally: word.Quit()这里的17是 Word 内部 PDF 格式常量我常用的环境里一直可以。但不同 Office 版本可能存在差异所以第一次使用前最好先用一个简单文档验证导出结果。特别提醒传给 Word COM 的路径一定要是绝对路径。很多报错不是路径不存在而是 Word 进程的工作目录和 Python 脚本的工作目录不一致。使用os.path.abspath或 pathlib 的resolve()都可以避免这个问题。如果转换的是只读文件或者文件被其他用户打开Word COM 可能弹窗可以在打开时加上参数也可以先用异常处理把错误记录下来。生产环境里我一般会保证源文件不被占用并提前建好输出目录。5.2 Linux 服务器用 LibreOffice 批量转换Linux 服务器没有 Word批量转 PDF 最常用的方式是 LibreOfficesoffice --headless --convert-to pdf --outdir ./pdf ./input/合同模板.docx注意--outdir参数要在输入文件前面否则有的版本会忽略输出目录。第一次执行时建议单文件测试。如果要在 Python 里批量处理可以调用 subprocessimport subprocess from pathlib import Path input_dir Path(input) output_dir Path(pdf) output_dir.mkdir(exist_okTrue) for src in input_dir.glob(*.doc*): subprocess.run([ soffice, --headless, --convert-to, pdf, --outdir, str(output_dir), str(src) ], checkTrue)LibreOffice 命令行转换适合“不保留复杂交互、但需要稳定出 PDF”的场景。对于复杂文档转换效果不一定和 Word 完全一致。比如某些带特殊字体、复杂文本框的页面可能发生字距变化或分页位置差异。所以不要在报告里写“完全一致”只能说“基本可用”。如果想把.doc转成.docx只需把--convert-to改成--convert-to docx后面再交给 python-docx 处理。5.3 其他格式转换和边界提醒经常有人把“PDF 转 Word”和“Word 转 PDF”混在一起问。实际上 PDF 转 Word 要麻烦得多。如果 PDF 本身就是从 Word 导出的可以尝试用 Word 打开 PDF 再另存为 docx但复杂版式会重排。如果是扫描版 PDF那就不是格式转换问题而是 OCR 问题需要配合文字识别工具去做。Markdown 转 Word 是另一个常见办公自动化工序。最简单的方案不是自己写解析器而是用 pandocpandoc 文档.md -o 文档.docx在 Python 里也可以用 subprocess 调用 pandoc适合把批量生成的 Markdown 报告统一转成 Word 交给业务方。需要注意目标机器上必须安装 pandoc否则命令会失败。转换场景里最容易忽略的是资源核验。生成 PDF 后不要只看文件存在还要检查 PDF 是否为空、页数是否合理、中文是否乱码。可以读取 PDF 第一页文本看是否包含关键标题也可以直接用 PyMuPDF 做简单校验。没有校验的批量转换最终很可能交出几十个全是空页的文件。6. 常见报错和排查顺序6.1 python-docx 打开文件失败如果Document(文件.doc)报PackageNotFoundError或BadZipFile原因基本是打开了一个旧版.doc或者把一个不支持的文件强行改成.docx。先检查文件真实格式再用 5.2 的方法转成 docx。如果文件路径存在但还是报错再看权限和文件占用。Windows 上某些被 Office 锁定的文件Python 不一定能读取Linux 上可能是目录权限不够。打印出完整路径用Path.exists()判断一下能把问题快速缩小。常见追加错误是代码开了文件但没关闭。批量处理时每打开一个 Document处理完要及时释放或者用with思想来组织代码防止句柄越积越多。python-docx 没有严格要求手动 close但如果长期批量运行建议及时把对象置空让垃圾回收机制更早工作。6.2 win32com 报找不到 Word 应用用win32com.client.Dispatch(Word.Application)时报COMError原因往往很简单机器上没有安装 Microsoft Word或者安装的是精简版 Word COM 没有正确注册。排查顺序手动打开一次 Word确认 Word 本身可用。看任务管理器里有没有残留的 WINWORD.EXE 进程有的话可以先正常关闭再重新跑脚本。如果手动能打开但脚本一直报错检查当前 Python 解释器是 32 位还是 64 位。Office、Python 位数不一致有时会导致 COM 调用异常。如果脚本部署在 Windows 服务或定时任务里Word 的 COM 对象可能受会话权限影响。常见做法是让服务以交互式用户身份运行或者在管理员手动登录的会话中执行任务而不是把 Word 当后台无界面服务长期运行。另外.doc文件里如果携带宏Word 的安全策略可能弹出“无法找到宏或宏被禁用”。对自动化任务来说最稳妥的方式是使用不含宏的干净模板或者先对源文档做信任处理。不要试图关闭 Word 所有安全机制那是危险动作。6.3 批量任务出现卡顿、缺文件、排版乱怎么办批量任务跑一半卡住或输出缺失首先要做的是定位问题而不是盲目重跑。我的排查顺序是看当前脚本停在哪个文件、哪一步先打印文件名和当前处理阶段。把异常用 try/except 捕获下来记录到一个日志文件里让脚本跳过失败文件继续处理。这样至少能保证第一批结果先出来。如果某类文件总是失败单独拿出一个源文件测试看是环境问题、数据问题还是模板问题。大批量处理 Word COM 时内存或句柄可能持续增长。我一般会每处理 50 到 100 个文件后重启一次 Word 进程具体间隔可以看机器内存和文件大小来调。输出排版乱时先检查源模板和字体。不要在自动化脚本里硬调一个偶然出现的格式问题除非你能确认这个现象在所有输出文件里稳定复现。办公自动化真正要交付的是“稳定、可复用、结果可验收”不是“跑了一次刚好成功”。所以每次批量任务我都要留下清晰的日志哪些文件成功哪些失败失败原因是什么。这样下次再跑时不用把全量文件重新扫一遍只处理失败清单就够了。最值得记住的一点是拿到一个 Word 文件先确认后缀是.doc还是.docx再决定用 python-docx 还是 pywin32。如果只是处理.docx先单条跑通再批量如果要处理.doc能转就先转成.docx。文档自动化最容易出错的不是 Python 语法而是对文档格式和运行环境的理解。把这个顺序理顺Word 相关的办公自动化任务就完成了一大半。
返回列表