为什么 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。

相关推荐
VidDown4 小时前
上传 2GB 文件别让 Django 接:分片上传、对象存储直传与秒传
python·django·音视频·状态模式·实时音视频·视频编解码·视频
打工仔折腾 AI4 小时前
FaceFusion本地换脸实战:Windows整合包、模型选择与遮罩调参记录
人工智能·windows·后端·python·深度学习·性能优化·ai agent 实战
yi0114 小时前
DAY24: LeetCode 167:两数之和 II|从暴力枚举到左右双指针
笔记·python·算法·leetcode·二分查找·双指针
kimnoic4 小时前
Python常用命令提示符使用方法详解
开发语言·python
扶风ff5 小时前
练题簿在线免费刷题:会员线下组卷、Word 试卷与成绩导入,课堂检测更方便
java·开发语言·算法·小程序·word
天天被压力5 小时前
【别再到处找免费股票数据API了:官方204个接口,32篇一次讲透 #25】北交所财务技术指标难搞?12个接口,报表+MACD全拿
java·人工智能·python
Είναι η κοπέλα5 小时前
模型量化完全指南:GGUF、GPTQ、AWQ 怎么选
人工智能·pytorch·python
yi0115 小时前
LeetCode 15:三数之和|排序 + 双指针,如何避免重复答案?
笔记·python·算法·leetcode·双指针
weixin199701080165 小时前
《1688消息服务落地方案:alibaba.message.* 与 Webhook 回调的可靠性与重试策略》(附Python源码)
开发语言·python