
为什么 python-docx 打不开加密的 .docxWord COM 却能打开一、根本原因两者打开文件的层级不同工具打开方式看到的是什么python-docx直接按 ZIP XML 解析文件在磁盘上的原始字节Word COM调用 Word 应用打开Word 解密后内存中的文档对象.docx加密后磁盘上的字节不再是标准 ZIP 包而是一个OLE 复合文档Compound File Binary Format内部包含加密流。python-docx 直接把它当 ZIP 打开自然读不到word/document.xml所以报Package not found。Word COM 走的是 Word 的完整加载流程先识别 OLE 容器 → 找到加密流 → 提示输入密码或使用已保存的凭据→ 解密 → 在内存中构建文档对象 → 你的代码才能操作doc.Tables。它根本不关心中间字节是什么格式。二、加密 .docx 的真实结构未加密.docxPK\x03\x04 ... ZIP 本地文件头 ├── [Content_Types].xml ├── _rels/.rels ├── word/document.xml ├── word/styles.xml └── ...加密.docxOffice 标准加密D0 CF 11 E0 A1 B1 1A E1 ... OLE Compound File 签名 ├── EncryptionInfo 加密算法、盐值、加密参数 ├── EncryptedPackage 被加密的原始 docx ZIP 字节流 └── DataSpaces/...所以文件头不是PK而是D0 CF 11 E0这是判断加密与否最直接的标志。整个原始 docx 被包在EncryptedPackage里AES 加密。解密需要的密钥由用户密码派生只有 Word/WPS 这类完整办公软件才知道怎么解。python-docx 完全不知道 OLE 容器、加密算法、密钥派生这套机制它只会在开头读 4 个字节发现不是PK就报错。三、python-docx 的加载流程Document(path)内部大致做这些事用zipfile.ZipFile(path)打开文件。检查是否为有效 ZIP。读取[Content_Types].xml。读取word/document.xml构建Document对象。只要第 1 步失败就会抛出PackageNotFoundError旧版是BadZipFile或KeyError。它没有“解密”这一步也没有密码接口。官方文档明确说python-docx 只处理Office Open XML 格式不支持加密文档。四、Word COM 为什么可以win32com.client.DispatchEx(Word.Application)启动的是完整的 Word 进程你调的Documents.Open()是 Word 自己的加载逻辑识别文件格式OLE 容器 vs 普通 ZIP。如果是加密文档弹出密码对话框或用已保存的凭据。解密EncryptedPackage。把解密后的字节当作 docx 处理。构建内存中的Document对象。你拿到的doc是 Word 内存模型和磁盘上的加密文件无关。保存时 Word 再把内存模型序列化回磁盘是否加密取决于SaveAs的参数。WPS 的 COM 接口也是同样的机制只是 ProgID 换成kwps.Application。五、实操中的判断与处理1. 判断文件是否加密读前 4 个字节PK\x03\x04→ 标准 ZIPpython-docx 可用。D0 CF 11 E0→ OLE 容器可能是加密 docx、也可能是老式.doc。其他 → 损坏或非 Word 文件。可以进一步检查 OLE 容器中是否含EncryptionInfo和EncryptedPackage流来区分“加密 docx”和“老 .doc”。2. 处理方式场景方案有密码且环境有 Word用 Word COMDocuments.Open(path, PasswordDocument...)有密码环境无 Word用msoffcrypto-tool解密后临时文件再交给 python-docx无密码只能人工处理或跳过批量优先 Word COM无 Word 时用 msoffcrypto-tool都失败则跳过并记录3. 用 msoffcrypto-tool 解密pipinstallmsoffcrypto-toolimportmsoffcryptofromioimportBytesIOwithopen(encrypted.docx,rb)asf:officemsoffcrypto.OfficeFile(f)office.load_key(passwordyour_password)bufBytesIO()office.decrypt(buf)withopen(decrypted.docx,wb)asf:f.write(buf.getvalue())# 之后再用 python-docx 打开 decrypted.docx4. 结合到你的工具现有代码里is_valid_docx_package已经检测到“文件头不是 PK”可以在此基础上若头是D0 CF 11 E0进一步区分是加密 docx 还是老 .doc用olefile列出流名含EncryptionInfo→ 加密 docx否则 → 老 .doc 或未知 OLE。加密 docx 时若有 Word COM 且 UI 提供了密码输入走 COM 打开否则尝试 msoffcrypto-tool都失败则标记skipped提示用户“文件已加密请提供密码或用 Word 另存为未加密版本”。六、为什么之前 Word COM 修复反而能成功之前try_repair_with_word_com里调用的就是 Word 自己的Documents.Opendocword.Documents.Open(path)doc.SaveAs2(tmp_path,FileFormatWD_FORMAT_XML_DOCUMENT)如果打开时 Word 记忆了密码例如文档来自你最近用 Word 打开过或密码为空它就能解密并另存为未加密 docx。然后 python-docx 再打开这个临时文件就没问题了。但如果Word 没记忆密码或弹出的密码对话框被DisplayAlerts0抑制Documents.Open直接失败或文档用的是 WPS 的私有加密算法Word 不认那修复就会失败此时会走到InvalidDocxError提示用户手动处理。七、一句话总结python-docxWord COM打开层级直接解析磁盘字节ZIP XML调用完整 Office 应用含解密加密 docx 表现不是 ZIP直接报Package not found识别 OLE → 解密 → 提供 Document 对象能否读密码不能能PasswordDocument参数或对话框能否解密不能能替代方案msoffcrypto-tool先解密有 Word 环境时首选所以python-docx 只处理未加密的 OOXML 文件加密文档必须交给能解密的一方——Word/WPS 的 COM或 msoffcrypto-tool 这类专用解密库——处理完之后再交回 python-docx。