每天学习一点Python——用PyPDF2玩转PDF文件-2
每天学习一点Python——用PyPDF2玩转PDF文件-2
工作中经常需要处理PDF文件——合并多份报告、插入目录页、旋转方向错误的页面、甚至给机密文件加密。
今天我们用PyPDF2库,逐行拆解四个实战场景。
一、合并多个PDF文件
from PyPDF2 import PdfMergerfrom pathlib import Pathreports_dir = ( Path.cwd()/ "practice_files"/ "expense_reports")
📌 逐行拆解:
• from PyPDF2 import PdfMerger→ 导入合并PDF所需的PdfMerger类
• from pathlib import Path→ 导入Path类,用于跨平台路径操作
• Path.cwd()→ 获取当前工作目录
• 连续两个/→ Path对象的路径拼接语法
expense_reports = list(reports_dir.glob("*.pdf"))expense_reports.sort()
📌 逐行拆解:
• .glob("*.pdf")→ 在reports_dir路径下匹配所有.pdf结尾的文件
• expense_reports→ 得到所有PDF文件的目录列表
• list()→ 将生成器转为列表
• .sort()→ 按文件名升序排序,确保合并顺序可控
⚠️ 关键点:如果reports_dir指向的是文件而非文件夹,.glob()会返回空列表
pdf_merger = PdfMerger()for path in expense_reports: pdf_merger.append(str(path)) # 也可以选择追加的页数 # pdf_merger.append(str(path), pages=(0, 3))
📌 逐行拆解:
• PdfMerger()→ 创建合并器对象
• append(str(path))→ 将PDF文件追加到合并器末尾
• 参数要求:文件路径字符串 或 文件对象
• 可选参数:pages=(0,3)→ 只取第1-3页(默认全部页面)
with Path("expense_reports.pdf").open(mode="wb") as output_file: pdf_merger.write(output_file)
📌 逐行拆解:
• Path("expense_reports.pdf").open(mode="wb")→ 以二进制写入模式创建输出文件
• pdf_merger.write(output_file)→ 将合并后的内容写入文件
✅ 执行结果:当前目录生成expense_reports.pdf,内含所有匹配的PDF文件
二、在指定位置插入PDF页面
report_dir = ( Path.cwd()/ "practice_files"/ "quarterly_report")report_path = report_dir/"report.pdf"toc_path = report_dir/"toc.pdf"
📌 逐行拆解:
• report_path→ 指向主报告
• toc_path→ 指向目录页PDF
pdf_merger = PdfMerger()pdf_merger.append(str(report_path))pdf_merger.merge(1, str(toc_path))
📌 逐行拆解:
• append()→ 先装入主报告
• merge(1, str(toc_path))→ 在索引1的位置插入目录
• 第一个参数:插入位置(0-based)→ 1表示原报告的第2页之前
• 与append()的区别:merge()是嵌入中间,不是追加到末尾
with Path("full_report.pdf").open(mode="wb") as output_file: pdf_merger.write(output_file)
✅ 执行结果:生成full_report.pdf
• 第1页:原报告首页• 第2页:目录• 之后:原报告剩余页
💡 易错Tip
merge()的位置索引是插入后该页将成为第几页。merge(1, toc)= 新文档的第2页是目录。建议:先在纸上画页码,再敲代码。
🧩 三、按奇偶页旋转页面
from PyPDF2 import PdfReader, PdfWriterpdf_path = Path.cwd()/"full_report.pdf"pdf_reader = PdfReader(str(pdf_path))pdf_writer = PdfWriter()
📌 逐行拆解:
• PdfReader()→ 读取PDF文件,参数需是路径字符串
• PdfWriter()→ 创建空白写入器,用于组装新PDF
for n in range(len(pdf_reader.pages)): page = pdf_reader.pages[n] if n % 2 == 0: page.rotate(-90) pdf_writer.add_page(page)
📌 逐行拆解:
• pdf_reader.pages→ 列表,包含所有页面对象
• len(pdf_reader.pages)→ 总页数
• n % 2 == 0→ 索引从0开始 → 对应第1、3、5页
• rotate(-90)→ 顺时针旋转90度(负值=顺时针,正值=逆时针)
• add_page(page)→ 将(可能旋转过的)页面加入写入器
with open("rotated_output.pdf","wb") as output_file: pdf_writer.write(output_file)
✅ 执行结果:生成rotated_output.pdf
• 第1、3、5页:顺时针旋转90度• 其他页:保持不变
💡 易错Tip•
rotate()会直接修改原页面对象• 若后续需要原始方向,务必提前复制页面• 旋转参数单位是度,必须是90的倍数
🔔 特别注意:这里的n从0开始,所以你会发现第1、3、5页进行了旋转。
四、检测并修复旋转过的页面
pdf_path = Path.cwd()/"rotated_output.pdf"pdf_reader = PdfReader(str(pdf_path))pdf_writer = PdfWriter()
📌 逐行拆解:
• 读取刚才生成的旋转版PDF,准备修复
for page in pdf_reader.pages: try: if page["/Rotate"] == -90: page.rotate(90) except KeyError: pass pdf_writer.add_page(page)
📌 逐行拆解:
• page["/Rotate"]→ 访问页面的旋转属性字典
• 若键不存在(页面未被旋转过)→ 抛出KeyError
• except KeyError: pass→ 跳过无旋转属性的页面
• page.rotate(90)→ 将-90度的页面逆时针回转90度,恢复正向
with open("output.pdf","wb") as output_file: pdf_writer.write(output_file)
✅ 执行结果:生成output.pdf,所有页面恢复原始方向
💡 易错Tip并非所有PDF页面都有
/Rotate键。若直接访问page['/Rotate']而不捕获异常,程序会崩溃。
也可用hasattr的方法:
pdf_path = Path.cwd()/"rotated_output.pdf"pdf_reader = PdfReader(str(pdf_path))pdf_writer = PdfWriter()for page in pdf_reader.pages: if hasattr(page, '/Rotate') and page['/Rotate'] == -90: page.rotate(90) pdf_writer.add_page(page)with open("output.pdf","wb") as output_file: pdf_writer.write(output_file)
五、为PDF添加密码保护
pdf_path = Path.cwd()/"full_report.pdf"pdf_reader = PdfReader(str(pdf_path))pdf_writer = PdfWriter()
📌 逐行拆解:
• 读取原始完整报告,准备加密
pdf_writer.append(pdf_reader)pdf_writer.encrypt(user_pwd="mnzxs")
📌 逐行拆解:
• append(pdf_reader)→ 将读取器中的所有页面复制到写入器
• encrypt(user_pwd="mnzxs")→ 设置用户密码
• 可选参数:owner_pwd= → 所有者密码(权限更高)use_128bit=True → 提升加密强度(默认40位RC4)
output_path = Path.cwd()/"full_report_protected.pdf"with output_path.open(mode="wb") as output_file: pdf_writer.write(output_file)
✅ 执行结果:生成full_report_protected.pdf打开时需输入密码mnzxs
六、解密受保护的PDF
pdf_path = Path.cwd()/"full_report_protected.pdf"pdf_reader = PdfReader(str(pdf_path))
📌 逐行拆解:
• 读取加密文件⚠️ 此时若直接访问pdf_reader.pages会抛出异常
success = pdf_reader.decrypt("mnzxs")
📌 逐行拆解:
• decrypt(password)→ 尝试用密码解密
• 返回值:1 或 PasswordType.USER_PASSWORD → 成功0 → 失败
• 解密成功后→ pdf_reader.pages才可访问
if success: print("解密成功") page = pdf_reader.pages[0]else: print("解密失败")
✅ 执行结果:
解密成功
💡 易错Tip•
decrypt()成功后,读取器对象会一直保持解密状态,无需再次解密•decrypt()不会修改源文件,只是让你能读取内容• 若要移除密码,必须用写入器另存新文件
七、总结
今天我们走完了PDF处理的完整链路:
|
|
|
|---|---|
| 合并 | PdfMerger().append() |
| 插入 | merge(位置, 文件) |
| 旋转 | page.rotate(±90) |
| 加密 | writer.encrypt(user_pwd=...) |
| 解密 | reader.decrypt(...) |
📌 两点牢记:
🔸 路径处理Path()拼接时——文件夹还是文件,心里要有数
🔸 页面索引程序员数数从0开始,用户看页从1开始
📦 资源获取提示
关注「码农自习室」,后台回复关键词 Python学习,即可获取本文完整代码,一起动手掌握高效编程的核心技巧!
❤️ 支持我们
如果觉得本文对你有帮助,欢迎点赞 + 关注,您的支持是我们持续创作优质内容的最大动力!
📚 学习资源说明
本文内容整理自《Python基础教程(第3版)》第13章,所有命令均已实测。
夜雨聆风
