为什么 python-docx 打不开加密的 .docx,Word COM 却能打开

为什么 python-docx 打不开加密的 .docx,Word 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 的真实结构

未加密 .docx:

复制代码
PK\x03\x04 ... (ZIP 本地文件头)
├── [Content_Types].xml
├── _rels/.rels
├── word/document.xml
├── word/styles.xml
└── ...

加密 .docx(Office 标准加密):

复制代码
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) 内部大致做这些事:

  1. 用 zipfile.ZipFile(path) 打开文件。
  2. 检查是否为有效 ZIP。
  3. 读取 [Content_Types].xml。
  4. 读取 word/document.xml 构建 Document 对象。

只要第 1 步失败,就会抛出 PackageNotFoundError(旧版是 BadZipFile 或 KeyError)。它没有"解密"这一步,也没有密码接口。

官方文档明确说:python-docx 只处理 Office Open XML 格式,不支持加密文档。


四、Word COM 为什么可以

win32com.client.DispatchEx("Word.Application") 启动的是完整的 Word 进程,你调的 Documents.Open() 是 Word 自己的加载逻辑:

  1. 识别文件格式(OLE 容器 vs 普通 ZIP)。
  2. 如果是加密文档,弹出密码对话框,或用已保存的凭据。
  3. 解密 EncryptedPackage。
  4. 把解密后的字节当作 docx 处理。
  5. 构建内存中的 Document 对象。

你拿到的 doc 是 Word 内存模型,和磁盘上的加密文件无关。保存时 Word 再把内存模型序列化回磁盘,是否加密取决于 SaveAs 的参数。

WPS 的 COM 接口也是同样的机制,只是 ProgID 换成 kwps.Application。


五、实操中的判断与处理

1. 判断文件是否加密

读前 4 个字节:

  • PK\x03\x04 → 标准 ZIP,python-docx 可用。
  • D0 CF 11 E0 → OLE 容器,可能是加密 docx、也可能是老式 .doc。
  • 其他 → 损坏或非 Word 文件。

可以进一步检查 OLE 容器中是否含 EncryptionInfo 和 EncryptedPackage 流,来区分"加密 docx"和"老 .doc"。

2. 处理方式

场景 方案
有密码,且环境有 Word 用 Word COM,Documents.Open(path, PasswordDocument="...")
有密码,环境无 Word 用 msoffcrypto-tool 解密后临时文件,再交给 python-docx
无密码 只能人工处理,或跳过
批量 优先 Word COM;无 Word 时用 msoffcrypto-tool;都失败则跳过并记录

3. 用 msoffcrypto-tool 解密

bash 复制代码
pip install msoffcrypto-tool
python 复制代码
import msoffcrypto
from io import BytesIO

with open("encrypted.docx", "rb") as f:
    office = msoffcrypto.OfficeFile(f)
    office.load_key(password="your_password")
    buf = BytesIO()
    office.decrypt(buf)

with open("decrypted.docx", "wb") as f:
    f.write(buf.getvalue())

# 之后再用 python-docx 打开 decrypted.docx

4. 结合到你的工具

现有代码里 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.Open:

python 复制代码
doc = word.Documents.Open(path)
doc.SaveAs2(tmp_path, FileFormat=WD_FORMAT_XML_DOCUMENT)

如果打开时 Word 记忆了密码(例如文档来自你最近用 Word 打开过),或密码为空,它就能解密并另存为未加密 docx。然后 python-docx 再打开这个临时文件就没问题了。

但如果:

  • Word 没记忆密码;
  • 或弹出的密码对话框被 DisplayAlerts=0 抑制,Documents.Open 直接失败;
  • 或文档用的是 WPS 的私有加密算法,Word 不认;

那修复就会失败,此时会走到 InvalidDocxError,提示用户手动处理。


七、一句话总结

python-docx Word 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。

相关推荐
日日大王学编程17 小时前
LangGraph 基本用法详解:从入门到实战
python·语言模型·langchain
威哥爱编程17 小时前
Python 智能体开发实战 04:模型答得挺漂亮,程序却读不懂?让回答直接变成能入库的 JSON
python·agent
全栈陈序员17 小时前
【Python】基础语法入门(三十)—— Web 开发入门:用 Flask 搭建第一个网站
前端·python·flask
全栈陈序员17 小时前
【Python】基础语法入门(三十三)—— 日志系统详解:用 logging 记录程序运行
java·开发语言·python
晗夜未央17 小时前
跨仓库 Skill 换台电脑就失效?用 Git remote 校验项目
人工智能·git·python
Zack_Liu17 小时前
code样例
pytorch·python·深度学习
2501_9307077817 小时前
如何使用C#代码替换 Word 文档中的文本
word
wusam17 小时前
基于HCL的python脚本编程(4)
网络·python·智能路由器
treasure14017 小时前
Django复习笔记(2026年10月)
后端·python·django·api·web·drf
xiaoqiangclub18 小时前
Python 的实验性 JIT 要生变?那个开关现在还该不该开
python·性能优化·jit·开发环境·cpython