当前位置: 代码网 > it编程>前端脚本>Python > Python处理PDF文档的两大功能库(PyPDF2/pdfplumber)的使用指南

Python处理PDF文档的两大功能库(PyPDF2/pdfplumber)的使用指南

2026年03月06日 Python 我要评论
一、工欲善其事:pypdf2 vs pdfplumber,如何选择?在处理pdf之前,我们需要理解不同库的定位。python生态中有多个pdf处理库,各有所长。根据我们的学习目标,主要聚焦于两个库:功

一、工欲善其事: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的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com