Day 44:【99天精通Python】操作 PDF (PyPDF2) - 文档处理的屠龙刀
前言
欢迎来到第44天!
在办公自动化领域,除了 Word 和 Excel,PDF是最常见的文件格式。它的特点是"所见即所得",在任何设备上打开格式都不会乱。但这也导致它很难编辑。
你是否遇到过这些需求?
- 把 10 个 PDF 文件合并成一个。
- 把一个几百页的 PDF拆分成单独的章节。
- 从 PDF 中提取文本内容(虽然效果不一定完美)。
- 给 PDF 加密或添加水印。
Python 提供了多个 PDF 处理库,其中最流行、最轻量级的是PyPDF2。今天我们就来掌握这把处理 PDF 的屠龙刀。
本节内容:
- 安装 PyPDF2
- 读取 PDF 信息(页数、元数据)
- 提取文本内容
- 拆分 PDF 页面
- 合并多个 PDF
- 加密与解密 PDF
一、安装 PyPDF2
注意:PyPDF2 在 3.0 版本后 API 有较大变动(如PdfFileReader改名为PdfReader)。本教程使用最新的 3.x 版本。
pipinstallPyPDF2二、读取 PDF 信息
我们需要一个测试文件sample.pdf。
fromPyPDF2importPdfReader# 1. 打开 PDFreader=PdfReader("sample.pdf")# 2. 获取总页数print(f"总页数:{len(reader.pages)}")# 3. 获取元数据 (作者、标题等)meta=reader.metadataprint(f"标题:{meta.title}")print(f"作者:{meta.author}")2.1 提取文本 (Extract Text)
# 获取第一页page=reader.pages[0]# 提取文本text=page.extract_text()print(text)注意:PDF 的本质是排版指令,而不是文本流。如果 PDF 是扫描件(全是图片),或者使用了特殊编码的字体,提取出来的可能是乱码或空字符串。对于扫描件,需要使用 OCR 技术(如
pytesseract),这超出了 PyPDF2 的能力范围。
三、拆分 PDF (Split)
假设我们要把每一页单独保存为一个 PDF 文件。
fromPyPDF2importPdfReader,PdfWriter reader=PdfReader("sample.pdf")fori,pageinenumerate(reader.pages):# 创建一个写入器writer=PdfWriter()# 将当前页添加到写入器writer.add_page(page)# 保存文件filename=f"page_{i+1}.pdf"withopen(filename,"wb")asf:writer.write(f)print(f"已保存:{filename}")四、合并 PDF (Merge)
把多个 PDF 合并成一个。PyPDF2 提供了更高级的PdfMerger类,比手动add_page更方便。
fromPyPDF2importPdfMergerimportos# 获取所有要合并的文件files=["report_part1.pdf","report_part2.pdf","report_part3.pdf"]merger=PdfMerger()forpdfinfiles:# append 可以直接追加文件merger.append(pdf)# 保存合并结果merger.write("report_final.pdf")merger.close()print("合并完成!")进阶:插入到指定位置
# 把 part2 插在 part1 的第 2 页之后merger.append("part1.pdf")merger.merge(2,"part2.pdf")# 在索引2的位置插入五、加密与解密
5.1 给 PDF 加密码
fromPyPDF2importPdfReader,PdfWriter reader=PdfReader("sample.pdf")writer=PdfWriter()# 将所有页复制到 writerforpageinreader.pages:writer.add_page(page)# 设置密码writer.encrypt("123456")# 保存withopen("locked.pdf","wb")asf:writer.write(f)5.2 读取加密的 PDF
如果直接读取加密文件,会抛出错误或获取不到内容。
reader=PdfReader("locked.pdf")ifreader.is_encrypted:print("文件已加密,尝试解密...")# 尝试解密 (返回 True/False)ifreader.decrypt("123456"):print(f"解密成功,页数:{len(reader.pages)}")else:print("密码错误!")六、实战练习:PDF 工具箱
编写一个命令行工具,根据用户输入实现合并或拆分功能。
importosfromPyPDF2importPdfReader,PdfWriter,PdfMergerdefsplit_pdf(filename):"""拆分 PDF 的每一页"""try:reader=PdfReader(filename)base_name=os.path.splitext(filename)[0]fori,pageinenumerate(reader.pages):writer=PdfWriter()writer.add_page(page)output=f"{base_name}_p{i+1}.pdf"withopen(output,"wb")asf:writer.write(f)print(f"拆分完成,共{len(reader.pages)}个文件。")exceptExceptionase:print(f"拆分失败:{e}")defmerge_pdfs(file_list,output_name="merged.pdf"):"""合并多个 PDF"""merger=PdfMerger()try:forpdfinfile_list:ifos.path.exists(pdf):merger.append(pdf)else:print(f"跳过不存在的文件:{pdf}")merger.write(output_name)print(f"合并完成:{output_name}")exceptExceptionase:print(f"合并失败:{e}")finally:merger.close()if__name__=="__main__":print("1. 拆分 PDF")print("2. 合并 PDF")choice=input("请选择: ")ifchoice=="1":path=input("请输入要拆分的 PDF 路径: ")split_pdf(path)elifchoice=="2":paths=input("请输入要合并的 PDF 路径 (用逗号分隔): ").split(",")# 去除空白paths=[p.strip()forpinpaths]merge_pdfs(paths)七、常见问题
Q1:提取的文本是乱码?
这通常是因为 PDF 缺少字体映射表(CMap),或者是图片型 PDF。
解决方案:PyPDF2 搞不定。尝试pdfplumber库(提取效果更好),或者pytesseract(OCR 识别)。
Q2:合并后文件体积变大?
PDF 内部可能包含重复的字体或图片资源。合并时 PyPDF2 有时无法完美去重。可以使用 Acrobat Pro 进行优化,或者尝试pikepdf库。
Q3:如何旋转页面?
# 顺时针旋转 90 度page.rotate(90)八、小结
关键要点:
- 拆分:
Reader读,Writer写。 - 合并:直接用
PdfMerger最快。 - 局限性:对文本提取支持有限,不适合处理扫描件。
九、课后作业
- 奇偶拆分:编写程序,将一个 PDF 拆分成两个文件:
odd.pdf(包含第 1,3,5… 页)和even.pdf(包含第 2,4,6… 页)。 - 页面旋转矫正:读取一个 PDF,将所有页面旋转 180 度(倒立),并保存为新文件。
- 水印添加 (挑战):结合 Day 42 (Pillow) 和 PyPDF2。先用 Pillow 生成一张透明的水印图片,转为 PDF,然后将水印 PDF 与目标 PDF 的每一页进行合并(Overlay)。(提示:
page.merge_page())。
下节预告
Day 45:进阶篇总结与展望- 我们的进阶之旅即将画上句号。明天我们将复盘进阶篇的核心知识体系,并为接下来的重头戏——实战篇 (数据分析与Web开发)做好准备!
系列导航:
- 上一篇:Day 43 - 发送邮件smtplib
- 下一篇:Day 45 - 进阶篇总结与展望(待更新)