Python处理PDF文档的两大功能库(PyPDF2/pdfplumber)的使用指南
一、工欲善其事:PyPDF2 vs pdfplumber,如何选择?
在处理PDF之前,我们需要理解不同库的定位。Python生态中有多个PDF处理库,各有所长。根据我们的学习目标,主要聚焦于两个库:
| 功能/库 | PyPDF2(及其继任者pypdf) | pdfplumber |
|---|---|---|
| 主要用途 | PDF操作修改:合并、拆分、旋转、加密、添加水印 | 精确提取结构化数据,特别是表格和文本 |
| 文本提取能力 | 基础(对复杂布局支持有限) | 高级(保留布局信息) |
| 表格提取 | ❌ 不支持 | ✅ 内置强大功能 |
| 文本位置/坐标 | ❌ 不支持 | ✅ 支持 |
| PDF操作 | ✅ 合并、拆分、加密、水印 | ❌ 只读,不支持修改 |
| 处理速度 | 快 | 一般(基于pdfminer) |
| 典型场景 | 文档整理、批量处理、添加水印 | 数据提取、发票解析、报表分析 |
选择建议 :
- 如果你的任务是操作PDF本身(合并多个文件、拆分成单页、添加水印、加密解密),PyPDF2是首选。
- 如果你的任务是从PDF中提取数据(文本内容、表格、特定位置的信息),pdfplumber是利器。
- 在实际项目中,两者经常配合使用:先用pdfplumber提取数据和结构,再用PyPDF2进行文档整理和输出。
开始之前,请确保安装所需库:
pip install pypdf2 pdfplumber
注意:PyPDF2的后续版本已更名为pypdf,API有所调整。本文基于稳定版PyPDF2编写,代码同样适用于pypdf(需注意类名变更,如PdfFileReader变为PdfReader)。
二、PDF文本提取——让机器人“读懂”PDF
2.1 为什么PDF文本提取有难度
PDF本质上是描述页面外观的指令集合,而不是像Word或HTML那样包含结构化文本。这意味着:
- 文本可能以碎片形式存储(一个单词拆成多个指令)
- 缺少段落、标题等语义信息
- 可能存在字体嵌入、编码转换问题
因此,选择正确的工具至关重要。
2.2 使用PyPDF2提取基础文本
PyPDF2提供了基础的文本提取功能,适合简单的、布局规整的PDF。
import PyPDF2
def extract_text_with_pypdf2(pdf_path):
"""使用PyPDF2提取PDF文本"""
with open(pdf_path, 'rb') as file:
# 创建PDF阅读器对象
reader = PyPDF2.PdfReader(file)
# 获取总页数
num_pages = len(reader.pages)
print(f"总页数: {num_pages}")
# 提取每一页文本
full_text = ""
for page_num in range(num_pages):
page = reader.pages[page_num]
text = page.extract_text()
full_text += f"\n--- 第 {page_num + 1} 页 ---\n{text}"
return full_text
# 使用示例
text = extract_text_with_pypdf2("report.pdf")
print(text[:500]) # 打印前500个字符
局限性:PyPDF2的文本提取基于PDF内部的文本绘制指令,对于复杂排版(如多列、表格、页眉页脚 交错),提取结果可能出现乱序或丢失。
2.3 使用pdfplumber高质量提取文本
pdfplumber基于pdfminer.six构建,但提供了更友好的API和更好的布局分析能力。
import pdfplumber
def extract_text_with_pdfplumber(pdf_path):
"""使用pdfplumber提取PDF文本,保留布局"""
full_text = ""
with pdfplumber.open(pdf_path) as pdf:
print(f"总页数: {len(pdf.pages)}")
for i, page in enumerate(pdf.pages):
# 提取文本(自动处理布局)
text = page.extract_text()
full_text += f"\n--- 第 {i+1} 页 ---\n{text}"
# 可选:提取单词及其位置信息
words = page.extract_words()
if words:
print(f"第{i+1}页包含{len(words)}个单词")
# 第一个单词的坐标信息示例
first_word = words[0]
print(f"首个单词: '{first_word['text']}' 位置: ({first_word['x0']}, {first_word['top']})")
return full_text
# 使用示例
text = extract_text_with_pdfplumber("复杂报表.pdf")
优势:pdfplumber能够更好地理解页面布局,提取的文本顺序更符合人类阅读习惯。
2.4 实战:提取表格数据并结构化
pdfplumber的核心优势在于表格提取。它能够自动识别表格的边界和单元格结构。
import pdfplumber
import pandas as pd
def extract_tables_to_dataframe(pdf_path):
"""提取PDF中的表格并转为DataFrame"""
all_tables = []
with pdfplumber.open(pdf_path) as pdf:
for page_num, page in enumerate(pdf.pages):
# 提取当前页的所有表格
tables = page.extract_tables()
for table_num, table in enumerate(tables):
print(f"第{page_num+1}页,表格{table_num+1},共{len(table)}行")
# 转为DataFrame(假设第一行为表头)
if len(table) > 0:
df = pd.DataFrame(table[1:], columns=table[0])
all_tables.append({
'page': page_num + 1,
'table_num': table_num + 1,
'dataframe': df
})
return all_tables
# 优化表格提取的参数设置
def extract_tables_with_settings(pdf_path):
"""使用自定义参数优化表格提取"""
results = []
with pdfplumber.open(pdf_path) as pdf:
for page in pdf.pages:
# 自定义表格检测策略
table_settings = {
"vertical_strategy": "lines", # 基于线条检测列
"horizontal_strategy": "lines", # 基于线条检测行
"snap_tolerance": 5, # 线条对齐容差
"edge_min_length": 3, # 最小线段长度
}
# 或者使用"text"策略(当表格没有明显边框时)
# table_settings = {
# "vertical_strategy": "text",
# "horizontal_strategy": "text",
# }
table = page.extract_table(table_settings)
if table:
results.append(table)
return results
# 使用示例
tables = extract_tables_to_dataframe("财务报表.pdf")
for item in tables:
print(item['dataframe'].head())
参数调优技巧:
vertical_strategy:可选"lines"(基于线条)、“text”(基于文本对齐)、“explicit”(显式指定)snap_tolerance:当线条与文本不完全对齐时的容差(像素),默认10- 对于无边框表格,使用"text"策略可能效果更好
2.5 处理扫描件PDF(OCR集成)
pdfplumber本身不支持OCR,但可以与pytesseract结合处理扫描件:
import pdfplumber
import pytesseract
from PIL import Image
def ocr_scanned_pdf(pdf_path):
"""对扫描件PDF进行OCR识别"""
text_results = []
with pdfplumber.open(pdf_path) as pdf:
for i, page in enumerate(pdf.pages):
# 将PDF页面转为图像
im = page.to_image(resolution=300)
# 使用pytesseract进行OCR
text = pytesseract.image_to_string(im.original, lang='chi_sim+eng') # 中英文
text_results.append({
'page': i + 1,
'text': text
})
return text_results
三、合并与拆分PDF——文件管理的自动化
3.1 批量合并PDF文件
合并PDF是办公中最常见的需求之一。比如将多个部门的周报合并为一份总报告。
import os
from PyPDF2 import PdfMerger
def merge_pdfs(input_folder, output_filename, pattern=None):
"""
合并指定文件夹中的所有PDF文件
Args:
input_folder: 包含PDF文件的文件夹路径
output_filename: 输出文件名
pattern: 文件名过滤模式,如"report_"开头的文件
"""
merger = PdfMerger()
merged_count = 0
# 获取文件夹下所有PDF文件
for filename in os.listdir(input_folder):
if filename.lower().endswith('.pdf'):
if pattern and pattern not in filename:
continue
file_path = os.path.join(input_folder, filename)
print(f"正在添加: {filename}")
try:
merger.append(file_path)
merged_count += 1
except Exception as e:
print(f"添加文件 {filename} 时出错: {e}")
# 写入合并后的文件
if merged_count > 0:
merger.write(output_filename)
merger.close()
print(f"合并完成!共合并 {merged_count} 个文件,保存为: {output_filename}")
else:
print("未找到可合并的PDF文件")
# 按指定顺序合并(指定文件列表)
def merge_pdfs_by_list(file_list, output_filename):
"""按指定文件列表顺序合并PDF"""
merger = PdfMerger()
for file_path in file_list:
if os.path.exists(file_path):
print(f"添加: {os.path.basename(file_path)}")
merger.append(file_path)
else:
print(f"文件不存在: {file_path}")
merger.write(output_filename)
merger.close()
print(f"合并完成!保存为: {output_filename}")
# 使用示例
merge_pdfs('./月度报告', '2025年一季度合并报告.pdf')
merge_pdfs_by_list(['封面.pdf', '目录.pdf', '正文.pdf', '附录.pdf'], '完整报告.pdf')
3.2 拆分PDF文件
有时我们需要从大型PDF中提取特定页面,比如只保留合同的第一页(签字页)。
from PyPDF2 import PdfReader, PdfWriter
def split_pdf_by_pages(input_pdf, output_pattern, pages_to_extract):
"""
提取指定页面并保存为新文件
Args:
input_pdf: 输入PDF路径
output_pattern: 输出文件名模式,如"提取_第{page}页.pdf"
pages_to_extract: 要提取的页码列表(从1开始)
"""
reader = PdfReader(input_pdf)
total_pages = len(reader.pages)
for page_num in pages_to_extract:
if page_num < 1 or page_num > total_pages:
print(f"页码 {page_num} 超出范围(1-{total_pages})")
continue
writer = PdfWriter()
# 注意:页码从0开始索引
writer.add_page(reader.pages[page_num - 1])
output_file = output_pattern.format(page=page_num)
with open(output_file, 'wb') as output_pdf:
writer.write(output_pdf)
print(f"已提取第 {page_num} 页,保存为: {output_file}")
def split_pdf_every_n_pages(input_pdf, n):
"""每n页拆分成一个文件(如每10页一个文件)"""
reader = PdfReader(input_pdf)
total_pages = len(reader.pages)
for start in range(0, total_pages, n):
end = min(start + n, total_pages)
writer = PdfWriter()
for page_num in range(start, end):
writer.add_page(reader.pages[page_num])
output_file = f"拆分_{start+1}-{end}.pdf"
with open(output_file, 'wb') as output_pdf:
writer.write(output_pdf)
print(f"已生成: {output_file} (第{start+1}-{end}页)")
# 使用示例
split_pdf_by_pages('年度报告.pdf', '第{page}页_摘要.pdf', [1, 5, 10])
split_pdf_every_n_pages('超大文档.pdf', 20)
3.3 实战:智能拆分工具
结合pdfplumber的文本查找功能,我们可以实现更智能的拆分——根据内容自动切分。
import pdfplumber
from PyPDF2 import PdfReader, PdfWriter
def split_pdf_by_keyword(input_pdf, keyword, output_prefix):
"""
根据关键字出现的页码拆分PDF
找到关键字出现的所有页码,每个关键页及其后续页直到下一个关键页之前,作为一个独立文件
"""
# 第一步:找出所有包含关键字的页码
keyword_pages = []
with pdfplumber.open(input_pdf) as pdf:
for i, page in enumerate(pdf.pages):
text = page.extract_text()
if keyword in text:
keyword_pages.append(i) # 保存0-based索引
print(f"第{i+1}页包含关键字: {keyword}")
if not keyword_pages:
print(f"未找到包含关键字'{keyword}'的页面")
return
# 第二步:根据关键页拆分
reader = PdfReader(input_pdf)
total_pages = len(reader.pages)
for idx, start_page in enumerate(keyword_pages):
writer = PdfWriter()
# 确定结束页:下一个关键页的前一页,或最后一页
end_page = keyword_pages[idx + 1] - 1 if idx + 1 < len(keyword_pages) else total_pages - 1
# 添加从start_page到end_page的所有页
for page_num in range(start_page, end_page + 1):
writer.add_page(reader.pages[page_num])
# 保存文件
output_file = f"{output_prefix}_第{idx+1}段_页{start_page+1}-{end_page+1}.pdf"
with open(output_file, 'wb') as output_pdf:
writer.write(output_pdf)
print(f"已生成: {output_file}")
# 使用示例:按"第一章"、"第二章"拆分书籍
split_pdf_by_keyword('Python教程.pdf', '第', '章节')
四、添加水印与高级操作——让PDF更专业
4.1 添加文字水印(使用reportlab+PyPDF2)
PyPDF2本身不支持创建新内容,因此添加水印的通用方法是:先用reportlab创建水印PDF,再与原文件合并。
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from PyPDF2 import PdfReader, PdfWriter
def create_watermark_pdf(watermark_text, output_path, page_size=letter):
"""
创建水印PDF文件
"""
c = canvas.Canvas(output_path, pagesize=page_size)
width, height = page_size
# 设置透明度和旋转
c.setFillAlpha(0.3) # 透明度30%
c.setFont("Helvetica", 60)
c.setFillColorRGB(0.5, 0.5, 0.5) # 灰色
# 旋转45度,居中显示
c.saveState()
c.translate(width/2, height/2)
c.rotate(45)
c.drawCentredString(0, 0, watermark_text)
c.restore()
c.save()
print(f"水印文件已创建: {output_path}")
def add_watermark_to_pdf(input_pdf, watermark_pdf, output_pdf):
"""
为PDF文件添加水印
"""
# 读取水印PDF
watermark_reader = PdfReader(watermark_pdf)
watermark_page = watermark_reader.pages[0]
# 读取原PDF
reader = PdfReader(input_pdf)
writer = PdfWriter()
# 为每一页添加水印
for page_num in range(len(reader.pages)):
page = reader.pages[page_num]
page.merge_page(watermark_page) # 合并水印
writer.add_page(page)
# 保存结果
with open(output_pdf, 'wb') as output_file:
writer.write(output_file)
print(f"水印添加完成!保存为: {output_pdf}")
# 支持中文的水印
def create_chinese_watermark(watermark_text, output_path, font_path='simsun.ttc'):
"""
创建支持中文的水印PDF
"""
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
# 注册中文字体
pdfmetrics.registerFont(TTFont('SimSun', font_path))
c = canvas.Canvas(output_path, pagesize=letter)
width, height = letter
c.setFillAlpha(0.2)
c.setFont('SimSun', 50)
c.setFillColorRGB(0.7, 0.7, 0.7)
# 居中旋转
c.saveState()
c.translate(width/2, height/2)
c.rotate(45)
c.drawCentredString(0, 0, watermark_text)
c.restore()
c.save()
# 使用示例
create_watermark_pdf("CONFIDENTIAL", "watermark.pdf")
add_watermark_to_pdf("report.pdf", "watermark.pdf", "report_confidential.pdf")
create_chinese_watermark("绝密", "chinese_watermark.pdf")
add_watermark_to_pdf("合同.pdf", "chinese_watermark.pdf", "合同_带水印.pdf")
4.2 旋转页面
from PyPDF2 import PdfReader, PdfWriter
def rotate_pdf_pages(input_pdf, output_pdf, rotation_degree=90, pages=None):
"""
旋转PDF页面
pages: 要旋转的页码列表,None表示所有页
"""
reader = PdfReader(input_pdf)
writer = PdfWriter()
for page_num in range(len(reader.pages)):
page = reader.pages[page_num]
if pages is None or (page_num + 1) in pages:
page.rotate(rotation_degree)
writer.add_page(page)
with open(output_pdf, 'wb') as output_file:
writer.write(output_file)
print(f"旋转完成!保存为: {output_pdf}")
# 使用示例:将第2页旋转90度
rotate_pdf_pages("扫描件.pdf", "扫描件_校正.pdf", rotation_degree=90, pages=[2])
4.3 加密与解密PDF
from PyPDF2 import PdfReader, PdfWriter
def encrypt_pdf(input_pdf, output_pdf, user_password, owner_password=None):
"""添加密码保护"""
reader = PdfReader(input_pdf)
writer = PdfWriter()
# 复制所有页面
for page in reader.pages:
writer.add_page(page)
# 加密
writer.encrypt(user_password, owner_password)
with open(output_pdf, 'wb') as output_file:
writer.write(output_file)
print(f"加密完成!保存为: {output_pdf}")
def decrypt_pdf(input_pdf, output_pdf, password):
"""解密PDF(需要知道密码)"""
reader = PdfReader(input_pdf)
if reader.is_encrypted:
reader.decrypt(password)
writer = PdfWriter()
for page in reader.pages:
writer.add_page(page)
with open(output_pdf, 'wb') as output_file:
writer.write(output_file)
print(f"解密完成!保存为: {output_pdf}")
# 使用示例
encrypt_pdf("财务报告.pdf", "财务报告_加密.pdf", "123456")
decrypt_pdf("财务报告_加密.pdf", "财务报告_解密.pdf", "123456")
五、实战案例:构建自动化PDF处理流水线
让我们综合三天所学,构建一个完整的PDF处理流程:从多个PDF中提取表格数据,合并分析结果,并添加水印和密码保护。
import os
import pandas as pd
import pdfplumber
from PyPDF2 import PdfMerger, PdfReader, PdfWriter
from reportlab.pdfgen import canvas
class PDFAutomationPipeline:
"""PDF自动化处理流水线"""
def __init__(self, input_folder, output_folder):
self.input_folder = input_folder
self.output_folder = output_folder
os.makedirs(output_folder, exist_ok=True)
def extract_all_tables(self):
"""从所有PDF中提取表格"""
all_data = []
for filename in os.listdir(self.input_folder):
if filename.endswith('.pdf'):
file_path = os.path.join(self.input_folder, filename)
print(f"处理文件: {filename}")
with pdfplumber.open(file_path) as pdf:
for page_num, page in enumerate(pdf.pages):
tables = page.extract_tables()
for table_num, table in enumerate(tables):
# 假设第一行是表头
if len(table) > 0:
df = pd.DataFrame(table[1:], columns=table[0])
all_data.append({
'source': filename,
'page': page_num + 1,
'table': table_num + 1,
'data': df
})
return all_data
def create_summary_pdf(self, all_tables, summary_pdf):
"""创建汇总PDF(使用reportlab绘制简单汇总)"""
from reportlab.lib.pagesizes import A4
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table as RLTable
from reportlab.lib.styles import getSampleStyleSheet
doc = SimpleDocTemplate(summary_pdf, pagesize=A4)
styles = getSampleStyleSheet()
story = []
# 添加标题
story.append(Paragraph("数据汇总报告", styles['Title']))
story.append(Spacer(1, 12))
# 汇总每个表格的基本信息
for idx, item in enumerate(all_tables[:5]): # 只显示前5个表格
story.append(Paragraph(f"表格 {idx+1}: 来源 {item['source']} 第{item['page']}页", styles['Heading2']))
# 将DataFrame转为列表
data = item['data'].values.tolist()
if data:
# 添加表头
table_data = [item['data'].columns.tolist()] + data[:5] # 只显示前5行
t = RLTable(table_data)
story.append(t)
story.append(Spacer(1, 12))
doc.build(story)
print(f"汇总PDF已创建: {summary_pdf}")
def run_pipeline(self):
"""执行完整流程"""
print("=== 步骤1: 提取所有表格 ===")
tables = self.extract_all_tables()
print(f"共提取 {len(tables)} 个表格")
print("\n=== 步骤2: 生成汇总PDF ===")
summary_path = os.path.join(self.output_folder, "汇总报告.pdf")
self.create_summary_pdf(tables, summary_path)
print("\n=== 步骤3: 添加水印 ===")
# 创建水印
watermark_path = os.path.join(self.output_folder, "watermark.pdf")
create_watermark_pdf("内部资料", watermark_path)
# 添加水印
watermarked_path = os.path.join(self.output_folder, "汇总报告_带水印.pdf")
add_watermark_to_pdf(summary_path, watermark_path, watermarked_path)
print("\n=== 步骤4: 加密保护 ===")
final_path = os.path.join(self.output_folder, "最终报告_加密.pdf")
encrypt_pdf(watermarked_path, final_path, "secure123")
print(f"\n✅ 所有处理完成!最终文件: {final_path}")
# 使用流水线
pipeline = PDFAutomationPipeline("./输入文件夹", "./输出文件夹")
pipeline.run_pipeline()
六、性能对比与最佳实践
6.1 性能对比
根据官方性能测试,不同库在处理大文件时差异显著:
- PyMuPDF:处理7031页PDF仅需3.05秒(合并/复制操作)
- PyPDF2:处理同样任务需要494秒,是PyMuPDF的160倍以上
- 文本提取:PyMuPDF比PyPDF2快约12倍
因此,如果追求极致性能,尤其是处理大文件,可以考虑PyMuPDF。但本文聚焦的PyPDF2和pdfplumber在API友好度和特定功能(如表格提取)上仍有不可替代的优势。
6.2 避坑指南
版本兼容性:PyPDF2 3.x版本已弃用PdfFileReader等旧类名,改用PdfReader/PdfWriter。本文代码兼容新旧版本。
内存管理:处理大文件时,及时释放对象:
reader = PdfReader(open('large.pdf', 'rb'))
# 处理完成后
del reader
异常处理:PDF文件可能损坏或加密,添加异常捕获:
try:
reader = PdfReader(file)
except PyPDF2.errors.PdfReadError as e:
print(f"PDF读取错误: {e}")
中文支持:
- 提取中文:确保pdfplumber能正确编码
- 生成中文水印:需注册中文字体文件(.ttc/.ttf)
表格提取调优:如果默认参数提取效果不佳,尝试调整snap_tolerance和策略。
以上就是Python处理PDF文档的两大功能库(PyPDF2/pdfplumber)的使用指南的详细内容,更多关于Python处理PDF的资料请关注脚本之家其它相关文章!
相关文章
Django admin 实现search_fields精确查询实例
这篇文章主要介绍了Django admin 实现search_fields精确查询实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧2020-03-03


最新评论