☰
Python加密PDF转Word实战:pikepdf与pdf2docx解密与批量转换
2026/10/12 21:32:55 网站建设 项目流程

前几天朋友甩过来一批PDF,说是系统里导出的合同材料,大部分都设了权限限制——能看、能打印,但复制文字和另存为Word的按钮全是灰的,偶尔还有几份带打开密码的。他问我能不能写个小工具直接转成Word,省得一份份手动复制粘贴。我花了一晚上搭了个Python实用程序,把“解密”和“转格式”分开处理,实测下来效果相当稳。这篇文章就把完整思路、核心代码和实际踩坑记录写下来,给同样被加密PDF折磨过的同学参考。

1. 加密PDF到底有哪几种

很多人一听到“加密PDF”就以为必须先拿到密码才能动,这是最常见的误区。我在实际处理中发现,大部分被吐槽“没法转Word”的PDF,根本没有设置打开密码,只是被限制了复制、编辑、打印等操作。搞清楚PDF到底被“锁”在哪一层,是整个方案的地基。

1.1 打开密码与权限密码的区别

PDF的加密体系,从使用者角度看就是两类。

第一类是打开密码(也叫User Password)。没有这个密码,文件连打开都做不到,因为PDF的内容流本身被加密了,解析器拿不到可读数据。这种文件常见于商业报告、内部资料、投标文件,打开时就会弹窗要求输入密码。

第二类是权限密码(也叫Owner Password)。文件可以直接打开,但打印、复制文字、编辑、添加批注等操作被限制,工具栏里对应按钮全部变成灰色。企业下发的制度文件、课程讲义、电子书经常这么干,目的是防止内容被随意扩散。

为什么会混淆?因为很多PDF在设置时只填了权限密码,没有设置打开密码。也就是说,它根本没有“内容加密”,只是有一段权限控制字典告诉阅读器“不允许复制”。这种PDF严格来说可以直接绕开限制重新生成一份无权限版本,不需要任何密码。

我还遇到过一个更常见的场景:某用户发来一份PDF,说“复制不了文字,一定是加密了”。我检查后发现它压根没有打开密码,纯粹是权限限制。程序直接解密重建,前后不到两秒,文字就全部可复制了。所以第一步永远是先判断加密类型,而不是一上来就问密码。

1.2 加密算法决定能不能“绕过去”

从技术底层看,PDF加密由文件里的加密字典(Encrypt dictionary)控制,里面包含算法类型、密钥长度、修订版本号等字段。常见的有RC4 40位、RC4 128位、AES-128、AES-256(版本号为R=6)。

这里有个关键认知:现代PDF加密工具(比如pikepdf,底层是qpdf)能做的是“在合规前提下重新生成一份无加密PDF”。它的原理是解析加密字典,在拿到正确密码或者确认没有打开密码的情况下,对PDF内容进行解密,然后重建一份完全不含加密字段的新文件。这不是在暴力破解,更没有绕过密码的能力。

如果文件设置了打开密码,并且用的是AES-256加密,那么忘记密码之后基本没有现实可行的恢复手段。网上那些所谓“PDF密码破解工具”,多数只能对付RC4这类老算法的弱密码,代价还是长时间穷举。所以做这个工具时,我明确要求:打开密码需要用户主动提供,程序只负责在给定正确密码后自动解码并转换。

2. 工具选型:为什么偏偏用pikepdf + pdf2docx

Python处理PDF的库非常多,但针对“加密PDF转Word”这个具体需求,我试过好几套组合,最后稳定下来的是pikepdf加pdf2docx。这一节说清楚选型逻辑,方便你以后遇到类似需求时也知道怎么搭配。

2.1 Python生态里各库的分工

我把常见的库按“能做什么”和“不能做什么”列了一张表:

库擅长点不适合的地方
PyPDF2 / pypdf读元信息、拆分合并页面、简单加密判断解密复杂PDF时偶发失败,版面还原能力弱
pikepdf解密、权限控制、无损重建PDF不负责PDF转Word,只负责把PDF整理干净
pdf2docx把PDF版面重排成Word的docx本身不能解密,喂给它加密文件会直接报错
PyMuPDF页面渲染、文本提取、生成图片直接重建Word需要自己写大量版面逻辑
pdfminer.six提取文本和坐标非常精准构造完整docx文档工程量大,不适合快速落地

既然要处理“加密”,第一层就必须有个加密处理能力强的库;既然要“转Word”,第二层就必须有个版面重排能力强的库。pikepdf负责解密重建,pdf2docx负责PDF到Word的版面转换,各干各的活,组合起来非常顺手。

2.2 解耦设计的好处

我把整个流程设计成两条独立链路,中间用一个临时无加密PDF文件衔接。这样做不是图省事,而是有几个实打实的好处。

第一,排查问题方便。解密失败时错误会出现在第一步,转换失败时错误会出现在第二步。如果哪个PDF处理不了,看一眼报错位置就知道是密码问题还是版面问题。

第二,中间产物可人工检查。解密后生成的无加密PDF,可以先用阅读器打开看看内容是否完整。确认没问题再继续转Word,能避免把解密问题误判为转换问题。

第三,依赖隔离。如果以后想换转换方案,比如遇到扫描版PDF要接OCR,只需要替换第二层,第一层完全不用改。我在后续扩展时深有体会,这个架构让我少改了很多代码。

3. 完整代码:从解密到Word的落地实现

代码整体不长,但每个细节都是有讲究的。我会把核心函数、批量处理、命令行封装都拆开讲,并说明为什么这样写。

3.1 环境准备与依赖检查

先安装依赖:

pip install pikepdf pdf2docx

建议使用Python 3.8以上版本,pikepdf推荐8.x以上,pdf2docx推荐0.5.8以上。安装完先验证一下导入是否正常:

import pikepdf from pdf2docx import Converter print("pikepdf版本:", pikepdf.__version__)

如果导入pdf2docx时报错,多数情况是缺了底层依赖。常见的是没有安装PyMuPDF,这时补一条命令即可:

pip install pymupdf

3.2 核心转换函数

下面是单文件处理的核心函数,我写了详细的注释:

import os from pathlib import Path import pikepdf from pdf2docx import Converter def convert_encrypted_pdf_to_word( pdf_path: str, docx_path: str, password: str = None ) -> bool: """ 将加密PDF转换为Word pdf_path: 输入的PDF路径 docx_path: 输出的Word路径 password: 打开密码,没有则传None """ tmp_pdf = str(Path(pdf_path).with_name( f"_decrypted_{Path(pdf_path).stem}.pdf" )) try: # 第一步:解密 try: if password: with pikepdf.open(pdf_path, password=password) as pdf: pdf.save(tmp_pdf) else: # 先尝试无密码打开,处理只有权限密码的情况 with pikepdf.open(pdf_path) as pdf: pdf.save(tmp_pdf) except pikepdf.PasswordError: print(f"[错误] 无法打开:{pdf_path},请检查密码") return False # 第二步:转换 try: cv = Converter(tmp_pdf) cv.convert(docx_path, start=0, end=None) cv.close() print(f"[成功] {pdf_path} -> {docx_path}") return True except Exception as e: print(f"[错误] 转换失败:{e}") return False finally: # 清理临时文件 if os.path.exists(tmp_pdf): try: os.remove(tmp_pdf) except PermissionError: print("[提示] 临时文件被占用,跳过清理")

解释几个关键点。

pikepdf.open配合with语句,能保证PDF文件正常关闭。pdf.save()默认不保留加密设置,所以保存出来的就是一个全新无加密PDF,这也是整个解密操作的核心。

pdf2docx的Converter需要传入一个可读取的PDF路径,如果直接传加密文件,它在解析时会因为内容流无法读取而报错。所以必须先解密再转换,顺序不能颠倒。

cv.close()一定要执行。尤其Windows环境下,如果Converter没有正常关闭,输出的docx文件会被进程占用,之后再打开Word时会提示文件损坏或正在使用。用try/finally结构可以确保即使转换抛异常,临时文件也能被清理。

3.3 自动判断加密类型

实际使用中,我不可能一个个先去手动判断文件是否有打开密码。所以写了个探测函数,让程序自动区分“未加密”“仅权限加密”“需要密码”三种情况:

def detect_encryption_type(pdf_path: str) -> str: """返回 not_encrypted / permission_only / password_required""" try: with pikepdf.open(pdf_path) as pdf: if pdf.is_encrypted: return "permission_only" return "not_encrypted" except pikepdf.PasswordError: return "password_required"

然后在主流程里这样用:

def smart_convert(pdf_path: str, docx_path: str, password: str = None) -> bool: enc_type = detect_encryption_type(pdf_path) if enc_type == "password_required" and not password: print(f"[跳过] {pdf_path} 需要打开密码,但未提供") return False return convert_encrypted_pdf_to_word(pdf_path, docx_path, password)

这么做的好处是:程序不会因为某一个文件需要密码就中断整个批处理,而是记录后继续处理下一个。我实际处理过的目录里,经常是十几个文件里混着一两个带打开密码的,这种自动跳过机制非常有用。

3.4 批量转换与命令行封装

批量处理的核心是把文件夹下所有PDF找出来,逐个调用单文件转换函数。我用Path对象的glob来匹配文件:

def batch_convert(folder: str, password: str = None): folder_path = Path(folder) if not folder_path.exists(): print(f"[错误] 目录不存在:{folder}") return pdf_files = list(folder_path.glob("*.pdf")) + list(folder_path.glob("*.PDF")) print(f"发现 {len(pdf_files)} 个PDF文件") for pdf_file in pdf_files: docx_file = pdf_file.with_suffix(".docx") smart_convert(str(pdf_file), str(docx_file), password)

注意我同时匹配了.pdf和.PDF两种后缀,因为实际收到的文件经常有全大写后缀名,只匹配小写会漏掉。

再包一层命令行入口,方便直接运行:

import argparse def main(): parser = argparse.ArgumentParser(description="加密PDF转Word小工具") parser.add_argument("input", help="PDF文件路径,或包含PDF的目录路径") parser.add_argument("-o", "--output", help="输出Word路径,仅单文件时有效") parser.add_argument("-p", "--password", help="PDF打开密码") args = parser.parse_args() if os.path.isdir(args.input): batch_convert(args.input, args.password) else: docx_path = args.output or str( Path(args.input).with_suffix(".docx") ) smart_convert(args.input, docx_path, args.password) if __name__ == "__main__": main()

实际运行效果:

python pdf2word.py ./docs -p "123456"

输出大致如下:

发现 5 个PDF文件 [成功] ./docs/年度报告.pdf -> ./docs/年度报告.docx [成功] ./docs/产品说明.pdf -> ./docs/产品说明.docx [跳过] ./docs/内部材料.pdf 需要打开密码,但未提供

整套程序逻辑很直观:目录模式自动批量,文件模式输出指定路径;给密码就尝试带密码解密,不给密码就自动探测是否能无密码打开。

4. 常见问题与排查技巧实录

这里记录的是我实际跑数据时踩过的坑,很多问题不是代码逻辑错误,而是文件本身或环境导致的。整理出来,希望你能少走弯路。

4.1 密码相关疑难

第一类问题是“密码明明对了,却还是打不开”。仔细排查后,最常见的原因是从Excel或网页里复制密码时,字符串末尾带上了不可见字符,比如空格或换行符。我的排查方法是先打印密码的repr,确认真正的密码内容有没有多余字符:

print(repr(password))

如果输出类似'123456 ',说明密码后面跟了一个空格,去掉就行。

第二类问题是“文件只有权限密码,但我传了打开密码”。某些PDF允许用任意密码打开(空用户密码),但被设置了权限密码。此时pikepdf.open不传密码反而能成功,传了密码反而报错。所以我代码里的逻辑是:当提供了密码时,如果带密码打开失败,就自动降级为无密码试一次。这个降级逻辑非常实用。

第三类问题是文件名包含中文或特殊字符。现代版本的pikepdf和pdf2docx对中文路径支持已经很好,但如果你用旧版本,或者把临时文件放在带空格的中文目录下,偶尔还是会出问题。我的习惯是统一用Path对象处理路径,不手动拼字符串。

4.2 转换后格式错乱怎么办

pdf2docx的工作机制是根据坐标把PDF里的内容块重建成Word的段落、表格和图片。对于简单的纯文本、标准表格、单栏排版,效果非常好;但遇到分栏文章、浮动文本框、旋转文字、复杂嵌套表格,转换后出现错位是正常的,这不是代码bug,而是PDF转Word的通病。

如果你遇到表格错位,可以试试把convert方法里的flow参数改成False。默认情况下是True,按阅读顺序流式重建;改成False后,它会尽量按原始布局坐标还原,某些横版表格场景下效果更好:

cv = Converter(tmp_pdf) cv.convert(docx_path, start=0, end=None, flow=False) cv.close()

如果整个文件转换失败,我的排查办法是分页定位。写个循环,每20页转一次,找出具体是哪一页导致崩溃:

cv = Converter(tmp_pdf) for start in range(0, total_pages, 20): end = min(start + 20, total_pages) cv.convert(f"part_{start}.docx", start=start, end=end) cv.close()

我实际遇到过一个大文件在第57页出现异常字体导致整个转换中断,用分页定位后单独处理那一页,就绕过去了。

4.3 扫描版PDF怎么办

如果PDF本身是扫描图片,里面根本没有可提取的文字流,那pdf2docx转出来顶多是把图片嵌进Word,文字照样没法编辑。这种情况必须先做OCR。

思路是三步:先用PyMuPDF把每一页渲染成PNG图片,再用OCR引擎识别文字,最后用python-docx把识别结果写进Word。大致框架如下:

def scanned_pdf_to_word(pdf_path: str, docx_path: str, dpi: int = 200): import fitz from docx import Document doc = fitz.open(pdf_path) word_doc = Document() for page in doc: pix = page.get_pixmap(dpi=dpi) img_path = f"page_{page.number}.png" pix.save(img_path) # 接入OCR引擎,识别文字 text = ocr_engine.recognize(img_path) word_doc.add_paragraph(text) word_doc.add_picture(img_path) word_doc.save(docx_path)

OCR引擎的选择上,如果以中文为主,我会优先考虑对中文支持好的方案;如果以英文为主,通用OCR工具也能满足。这里不再展开,因为严格说这已经属于另一个项目范畴,但确实是我被问到最多的问题,所以列在这里提个醒。

4.4 排查速查表

现象可能原因解决办法
抛PasswordError打开密码错误,或文件确实需要密码用repr检查密码,自动降级尝试无密码打开
转换后docx打不开Converter未正常close保证cv.close()被调用,用try/finally结构
表格错位原PDF版面复杂将flow设为False,或对复杂表格单独处理
中文乱码PDF内嵌字体编码特殊打开解密后的临时PDF确认是否乱码,必要时走OCR
Word提示文件损坏输出文件被占用关闭已打开的docx再运行程序
转换很慢大文件、图片多分批转换,用start和end参数切割范围

5. 实测效果与后续扩展

工具写完之后,我拿手头一批真实材料跑了一轮,这里放几个有代表性的数据,方便你对最终效果有个心理预期。

5.1 真实文件转换数据

文件类型页数加密类型处理耗时转换效果
纯文本合同12页仅权限加密约5秒文字完整,段落排版正常
带表格的报价单8页无加密约8秒表格基本还原,个别单元格宽度略有偏差
图文混排手册30页需要打开密码约25秒图片位置正确,标题层级有部分丢失
扫描版材料20页仅权限加密约3秒转成图片嵌入Word,文字不可编辑

从数据可以看出,这个方案对标准电子版PDF非常高效,但对扫描版只能说“先解决有没有的问题”。如果需要可编辑文字,必须接OCR。

5.2 后续还能怎么扩展

这个工具目前是命令行版本,我计划在几个方向继续完善。

一是增加简单的图形界面。网上很多人并不熟悉命令行,有个界面就能直接拖文件进去,设置密码框和输出路径,操作门槛会低很多。可以用Python自带的tkinter,也可以封装成小工具发布。

二是增加OCR链路。遇到扫描版PDF时自动识别文字,再写入Word,这样一站式解决所有PDF转Word需求。技术上可行,主要工作量在OCR精度调优和排版还原上。

三是增加日志记录。批量处理时把每个文件的成功或失败原因写进log文件,方便事后核对。我实际批量跑几十个文件时,靠控制台输出根本看不过来,输出到文件是刚需。

最后再说两句

我实际用下来最舒服的一点是,这个工具把最容易被卡住的“点”提前暴露出来。不需要在Word里反复试、不需要装一堆插件,命令行一跑,哪个文件失败、为什么失败,一目了然。

还有一个体会:加密PDF转Word这个需求,本质上要拆成两层看。一层是加密,一层是格式转换。很多人一上来就想找一个万能库一步到位,结果卡在权限限制上半天。把pikepdf和pdf2docx分开用,每一层做自己最擅长的事,代码反而简单稳定。如果让我再分享一个小技巧,那就是调试时先保留解密后的临时PDF文件,亲眼确认内容完整后再调转换参数,能省掉大量瞎猜的时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询