2、5分钟上手|PyPDF2 快速提取PDF文本

5分钟上手|PyPDF2 快速提取PDF文本(保姆级图文教程)

入门首选|一行代码读PDF,一篇文章全搞定

前言

在日常办公和数据处理中,PDF文件几乎无处不在。合同、报告、论文、发票......每天都有大量PDF文档需要处理。当我们需要从中提取文字信息时,手动复制粘贴不仅效率低下,而且容易出错。

Python的PyPDF2库,正是解决这一痛点的利器。它轻量、纯Python实现、无需安装额外依赖,是初学者入门PDF自动化处理的最佳选择。

本文将带你从零开始,用PyPDF2快速提取PDF文本,涵盖安装配置、核心操作、元数据获取、中文乱码避坑等全流程。全程附代码和图示,确保你读完就能上手写代码。

一、PyPDF2 是什么?适用场景与核心优缺点

1.1 适用场景

PyPDF2是一个纯Python的PDF处理库,能够在不借助任何外部软件的情况下,对PDF文件进行各种操作[reference:0]。它在以下场景中表现出色:

  • 批量提取PDF文本:从成百上千份报告中提取关键词、摘要或全文
  • 合并/拆分PDF:将多个PDF合并为一个文档,或将大型PDF按页拆分
  • 读取PDF元信息:获取文档的作者、标题、创建时间等信息
  • 加密/解密PDF:为敏感文档添加密码保护
  • 页面旋转、裁剪、添加水印等基础页面操作

1.2 核心优缺点

维度 评价 说明
✅ 优点 轻量易上手 纯Python实现,一行pip安装,API简洁直观
✅ 优点 零外部依赖 无需安装Adobe Acrobat或其他PDF软件
✅ 优点 基础功能全覆盖 合并、拆分、提取、加密等一应俱全
❌ 缺点 中文易乱码 对中文字体编码支持较弱,这是最常见的坑
❌ 缺点 文本提取能力有限 对复杂排版、表格、扫描件基本失效
❌ 缺点 大文件内存占用高 将整个PDF加载到内存,几百页可能卡死

特别提醒 :PyPDF2对中文PDF提取文本时容易产生乱码。官方原版PyPDF2已停止维护,推荐使用其现代分支 pypdf (API几乎完全兼容)[reference:1]。下文代码同时支持两种写法,你只需将 import PyPDF2 替换为 import pypdf 即可平滑迁移。

1.3 与其他PDF处理库的对比

为了让读者更清楚PyPDF2的定位,下图展示了主流Python PDF库的对比:
全能高性能类
文本提取类
提取失败换
性能不够换
需要OCR换
PyPDF2/pypdf

轻量入门
pdfplumber

表格/中文更稳
PDFMiner.six

布局分析强
PyMuPDF/fitz

速度最快
pikepdf

压缩/对象复用
PaddleOCR/Tesseract

一句话选择建议:日常基础操作用PyPDF2/pypdf;提取中文或表格换pdfplumber;追求性能或处理大型PDF用PyMuPDF;扫描件PDF直接上OCR。

二、环境准备:pip安装 + 虚拟环境配置

2.1 基础安装

打开终端/命令提示符,执行以下命令:

bash 复制代码
pip install PyPDF2

如果你决定使用现代分支pypdf(强烈推荐),执行:

bash 复制代码
pip install pypdf

验证安装是否成功:

bash 复制代码
python -c "import PyPDF2; print(PyPDF2.__version__)"
# 输出类似:3.0.0

2.2 虚拟环境配置(团队项目推荐)

bash 复制代码
# 创建虚拟环境
python -m venv pdf_env

# 激活虚拟环境(Windows)
pdf_env\Scripts\activate

# 激活虚拟环境(Mac/Linux)
source pdf_env/bin/activate

# 在虚拟环境中安装
pip install PyPDF2

# 导出依赖清单
pip freeze > requirements.txt

2.3 安装常见问题

错误信息 原因 解决方案
No module named 'PyPDF2' 未安装库 执行 pip install PyPDF2
ImportError: cannot import name 'PdfFileReader' 新版API变更 改用 from PyPDF2 import PdfReader
pip install 超时 网络问题 换国内镜像:pip install PyPDF2 -i https://pypi.tuna.tsinghua.edu.cn/simple

三、核心操作:逐页读取PDF文本

3.1 初学者最易犯的错误

很多新手会这样写:

python 复制代码
import PyPDF2
with open('example.pdf', 'rb') as file:
    reader = PyPDF2.PdfReader(file)
    print(reader)  # 输出:<PyPDF2._reader.PdfReader object at 0x...>

打印出来的只是一个内存地址,而不是PDF的内容[reference:2]。这是因为PdfReader对象代表的是整个PDF文档的结构,而不是文本本身。

3.2 正确的文本提取流程

下图清晰展示了正确的文本提取流程:
渲染错误: Mermaid 渲染失败: Parse error on line 5: ...D -->|.extract_text\(\)| E[提取的文本字符串] -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'

3.3 逐页提取完整代码

python 复制代码
import PyPDF2

def extract_text_from_pdf(pdf_path):
    """从PDF文件中提取所有文本并打印"""
    try:
        with open(pdf_path, 'rb') as file:
            reader = PyPDF2.PdfReader(file)
            print(f"文件共 {len(reader.pages)} 页\n")
            
            for page_num, page in enumerate(reader.pages):
                text = page.extract_text()
                print(f"--- 第 {page_num + 1} 页 ---")
                print(text if text else "[该页无文本或提取失败]")
                print()
                
    except FileNotFoundError:
        print(f"错误:文件 '{pdf_path}' 不存在")
    except Exception as e:
        print(f"处理PDF时发生错误:{e}")

# 使用示例
extract_text_from_pdf('example.pdf')

3.4 只提取指定页面

python 复制代码
# 只提取第3页(索引从0开始,所以第3页的索引是2)
reader = PyPDF2.PdfReader('example.pdf')
third_page_text = reader.pages[2].extract_text()
print(third_page_text)

3.5 合并多个PDF文件

python 复制代码
from PyPDF2 import PdfReader, PdfWriter

def merge_pdfs(pdf_list, output_path):
    writer = PdfWriter()
    
    for pdf_file in pdf_list:
        reader = PdfReader(pdf_file)
        for page in reader.pages:
            writer.add_page(page)
    
    with open(output_path, 'wb') as f:
        writer.write(f)
    
    print(f"合并完成!共 {len(writer.pages)} 页,保存至 {output_path}")

# 使用示例
merge_pdfs(['file1.pdf', 'file2.pdf', 'file3.pdf'], 'merged.pdf')

3.6 拆分PDF文件

python 复制代码
from PyPDF2 import PdfReader, PdfWriter

def split_pdf(input_pdf, output_dir):
    reader = PdfReader(input_pdf)
    
    for i, page in enumerate(reader.pages):
        writer = PdfWriter()
        writer.add_page(page)
        
        output_path = f"{output_dir}/page_{i+1}.pdf"
        with open(output_path, 'wb') as f:
            writer.write(f)
    
    print(f"拆分完成!共 {len(reader.pages)} 个文件")

# 只拆分前5页
def split_first_n_pages(input_pdf, n, output_dir):
    reader = PdfReader(input_pdf)
    writer = PdfWriter()
    
    for page in reader.pages[:n]:
        writer.add_page(page)
    
    with open(f"{output_dir}/first_{n}_pages.pdf", 'wb') as f:
        writer.write(f)

四、进阶:获取PDF元信息

PDF文件通常包含元数据(Metadata),如作者、标题、创建工具、加密状态等。PyPDF2可以轻松读取这些信息。

4.1 获取完整元信息

python 复制代码
from PyPDF2 import PdfReader

reader = PdfReader('example.pdf')
info = reader.metadata

print("=" * 40)
print("PDF 元信息")
print("=" * 40)
print(f"标题: {info.title}")
print(f"作者: {info.author}")
print(f"主题: {info.subject}")
print(f"创建者: info.creator}")
print(f"生产者: info.producer}")
print(f"页数: {len(reader.pages)}")
print(f"是否加密: {reader.is_encrypted}")
print("=" * 40)

4.2 处理加密PDF

python 复制代码
from PyPDF2 import PdfReader

reader = PdfReader('encrypted.pdf')

if reader.is_encrypted:
    try:
        # 尝试解密
        reader.decrypt('your_password')
        print("解密成功!")
        
        # 现在可以正常提取文本
        text = reader.pages[0].extract_text()
        print(text)
    except Exception as e:
        print(f"解密失败:{e}")
else:
    print("文件未加密,直接读取")

⚠️ 重要提示 :PyPDF2对加密PDF的支持有限,仅支持旧式加密算法(RC4),不支持AES-256等现代加密方式[reference:3]。遇到 NotImplementedError: only algorithm code 1 and 2 are supported 错误时,说明文件使用了不支持的加密算法,建议换用pikepdf处理[reference:4]。

五、避坑指南:中文乱码的根源与解决方案

这是初学者在使用PyPDF2时最常踩的坑,值得花一整节来讲清楚。

5.1 乱码现象长什么样?

python 复制代码
# 原本应该是:"这是一份中文PDF文档"
# 实际输出:
"ä¸è¿½ä>>½ä¸æPDFææ£"
# 或者输出:
"?????????"

5.2 中文乱码的根源(重点理解)

乱码的根本原因并不复杂。下图清晰解释了为什么PyPDF2会提取出乱码:
PyPDF2提取过程
PDF内部结构
存储在字体字典中
需要ToUnicode CMap映射
只读取字形编码
无CMap映射表
乱码/问号/方框
CMap缺失
PDF中的中文字符
字形编码/字符索引
Unicode字符
PyPDF2读取PDF
得到原始编码值
无法转换为Unicode
输出乱码

简单来说:PDF中的中文字符存储的是一串"编号",需要借助"编码映射表"才能翻译成正确的中文。PyPDF2恰恰缺少这个翻译能力,遇到非标准编码的中文字体就直接"放弃治疗",输出占位符[reference:5]。

具体来说,乱码的根源有以下几种:

  1. 字体未嵌入或嵌入不全:PDF使用外部字体,提取时找不到对应关系
  2. 编码映射表(ToUnicode CMap)缺失:字符代码无法映射到Unicode
  3. PyPDF2本身不支持复杂字体解码:遇到CID字体、自定义编码的中文字体时直接输出乱码[reference:6]
  4. 编码方式不匹配:PDF实际使用GBK编码,而库默认按UTF-8解码

5.3 如何判断PDF是否"可提取"?

在执行代码之前,建议先手动测试一下PDF的文字是否可选:

python 复制代码
from PyPDF2 import PdfReader

def check_pdf_extractability(pdf_path):
    reader = PdfReader(pdf_path)
    
    print(f"文件名: {pdf_path}")
    print(f"页数: {len(reader.pages)}")
    print(f"是否加密: {reader.is_encrypted}")
    
    # 尝试提取第一页的前200个字符
    sample = reader.pages[0].extract_text()[:200]
    print(f"第一页预览: {sample}")
    
    # 检查生产者信息
    producer = reader.metadata.get('/Producer', '未知')
    print(f"生产者: {producer}")
    
    # 初步判断
    if any(ord(c) > 127 for c in sample if c):
        print(">>> 可能包含非ASCII字符(中文),提取结果可能存在乱码风险")
    else:
        print(">>> 仅包含ASCII字符,提取相对可靠")

check_pdf_extractability('test.pdf')

5.4 中文乱码的临时解决方案

虽然PyPDF2无法完美解决中文乱码,但在某些场景下可以通过以下方式缓解:

方案1:使用pdfplumber替代(推荐)
python 复制代码
# pip install pdfplumber
import pdfplumber

with pdfplumber.open('chinese_doc.pdf') as pdf:
    for page in pdf.pages:
        text = page.extract_text()
        print(text)

pdfplumber基于PDFMiner,对中文字体支持更稳定,API也非常相似[reference:7]。

方案2:使用pypdf(现代分支)加容错参数
python 复制代码
from pypdf import PdfReader

# 添加strict=False忽略部分解析警告
reader = PdfReader('chinese_doc.pdf', strict=False)
text = reader.pages[0].extract_text()
方案3:处理扫描件PDF(图片型PDF)
python 复制代码
# 图片型PDF需要先转换为图片,再用OCR识别
# pip install pdf2image pytesseract pillow
from pdf2image import convert_from_path
import pytesseract

images = convert_from_path('scanned.pdf')
text = pytesseract.image_to_string(images[0], lang='chi_sim')
print(text)
方案4:编码后处理
python 复制代码
# 尝试手动修复编码(仅适用于特定场景)
def fix_encoding(broken_text):
    # 尝试不同的编码方式
    for encoding in ['gbk', 'gb2312', 'big5', 'utf-8']:
        try:
            return broken_text.encode('latin1').decode(encoding)
        except:
            continue
    return broken_text

text = page.extract_text()
fixed_text = fix_encoding(text)

5.5 避坑总结

问题类型 表现 解决方案
文字型PDF 输出乱码(如䏿‡) 换用pdfplumber
扫描件PDF 提取为空或全是乱码 走OCR路线
加密PDF 报NotImplementedError 换pikepdf解密
大文件 内存暴涨/卡死 分批处理或换pypdf

六、完整可运行代码:批量处理文件夹内所有PDF

以下是一个生产环境可直接使用的脚本,一键提取文件夹内所有PDF的文本内容,并保存为独立的TXT文件。

python 复制代码
import os
import sys
from pathlib import Path
from PyPDF2 import PdfReader

def extract_text_from_pdf(pdf_path):
    """
    从单个PDF文件提取文本
    返回: (是否成功, 文本内容)
    """
    try:
        reader = PdfReader(pdf_path)
        full_text = []
        
        for page_num, page in enumerate(reader.pages, 1):
            text = page.extract_text()
            if text:
                full_text.append(f"[第{page_num}页]\n{text}\n")
            else:
                full_text.append(f"[第{page_num}页]\n[无文本或提取失败]\n")
        
        return True, "\n".join(full_text)
    
    except Exception as e:
        return False, f"处理失败:{str(e)}"

def batch_extract_pdfs(input_folder, output_folder, extensions=None):
    """
    批量提取文件夹内所有PDF的文本
    
    参数:
        input_folder: 存放PDF文件的文件夹路径
        output_folder: 输出TXT文件的文件夹路径
        extensions: 文件扩展名列表,默认为['.pdf']
    """
    if extensions is None:
        extensions = ['.pdf', '.PDF']
    
    # 创建输出文件夹
    Path(output_folder).mkdir(parents=True, exist_ok=True)
    
    # 收集所有PDF文件
    pdf_files = []
    for ext in extensions:
        pdf_files.extend(Path(input_folder).glob(f"*{ext}"))
    
    if not pdf_files:
        print(f"在文件夹 '{input_folder}' 中未找到PDF文件")
        return
    
    print(f"共找到 {len(pdf_files)} 个PDF文件,开始处理...\n")
    
    success_count = 0
    fail_count = 0
    
    for pdf_file in pdf_files:
        print(f"正在处理: {pdf_file.name} ...")
        
        success, content = extract_text_from_pdf(str(pdf_file))
        
        output_path = Path(output_folder) / f"{pdf_file.stem}.txt"
        
        with open(output_path, 'w', encoding='utf-8') as f:
            f.write(content)
        
        if success:
            success_count += 1
            print(f"  ✓ 成功,已保存至: {output_path.name}")
        else:
            fail_count += 1
            print(f"  ✗ 失败: {content[:100]}")
    
    print(f"\n{'='*50}")
    print(f"处理完成!成功: {success_count},失败: {fail_count}")
    print(f"输出目录: {output_folder}")
    print(f"{'='*50}")

def extract_with_progress(input_folder, output_folder, show_preview=True):
    """
    带进度预览的批量提取功能
    """
    pdf_files = list(Path(input_folder).glob("*.pdf")) + list(Path(input_folder).glob("*.PDF"))
    total = len(pdf_files)
    
    for idx, pdf_file in enumerate(pdf_files, 1):
        print(f"[{idx}/{total}] 处理: {pdf_file.name}")
        
        success, content = extract_text_from_pdf(str(pdf_file))
        
        if show_preview and success:
            preview = content[:200].replace('\n', ' ')
            print(f"  预览: {preview}...")
        
        output_path = Path(output_folder) / f"{pdf_file.stem}.txt"
        with open(output_path, 'w', encoding='utf-8') as f:
            f.write(content)
        
        print(f"  ✓ 已保存\n")

if __name__ == "__main__":
    # 方式1:直接运行
    INPUT_DIR = "./pdfs"      # 存放PDF的文件夹
    OUTPUT_DIR = "./output"   # 输出TXT的文件夹
    
    # 方式2:命令行传参
    if len(sys.argv) >= 3:
        INPUT_DIR = sys.argv[1]
        OUTPUT_DIR = sys.argv[2]
    
    batch_extract_pdfs(INPUT_DIR, OUTPUT_DIR)
    
    # 如果需要更详细的处理进度,使用以下函数
    # extract_with_progress(INPUT_DIR, OUTPUT_DIR, show_preview=True)

七、总结与延伸建议

7.1 本文核心要点回顾

知识点 关键内容
安装配置 pip install PyPDF2,推荐同时安装pdfplumber作为备选
文本提取 三步走:PdfReaderpages.extract_text()
合并/拆分 使用PdfWriter添加/写入页面
元数据 reader.metadata获取标题、作者等信息
中文乱码 根源在于字体编码映射缺失,推荐pdfplumber替代

7.2 PyPDF2 vs pypdf 版本选择建议

版本 维护状态 推荐度 适用场景
PyPDF2 1.x~2.x 已停止 不推荐
PyPDF2 3.x 最终版 ⭐⭐ 短期使用
pypdf 活跃更新 ⭐⭐⭐⭐⭐ 强烈推荐

只需将 import PyPDF2 改为 import pypdffrom PyPDF2 import xxx 改为 from pypdf import xxx,代码几乎无需其他改动即可平滑迁移。

7.3 进阶学习方向

  • pdfplumber:提取中文PDF文本、表格数据的首选替代方案
  • PyMuPDF:追求极致性能时使用,速度远超PyPDF2
  • pikepdf:处理加密PDF、优化合并后体积时使用
  • PaddleOCR:处理扫描件PDF时使用,中英文识别准确率高

💡 一点提醒 :任何工具都有其边界。PyPDF2最适合处理西文、无加密、文本型PDF的轻量级操作。遇到中文乱码、扫描件、大文件等场景,及时切换工具往往比"硬扛"更高效。


恭喜你!读完这篇教程,你已经掌握了PyPDF2的核心用法。赶紧打开终端,安装PyPDF2,动手试试从你的第一份PDF中提取文本吧!

如果遇到任何问题,欢迎在评论区留言交流~

相关推荐
2501_921649492 小时前
低延迟量化交易数据 API:从架构设计到性能优化的完整实践指南
python·websocket·金融·量化
小元宝的专属厨师2 小时前
day08_LinkedList与队列栈
java
薛定谔的悦2 小时前
IEC 60870-5-104协议解析——电力系统远动通信实战
linux·状态模式·储能·ems
ꪶꪜ4452 小时前
vlan综合实验
linux·运维·网络
Jackyzhe2 小时前
从零学习Kafka:位移与高水位
分布式·学习·kafka
代码的乐趣2 小时前
支持selenium的chrome driver更新到147.0.7727.56
chrome·python·selenium
白露与泡影2 小时前
Spring Boot 缓存架构:一行配置切换 Caffeine 与 Redis,透明支持多租户隔离
spring boot·缓存·架构
Han_han9193 小时前
案例二:交通工具调度系统(核心:继承 + 多态 + final + 方法重写)
java·开发语言
咋吃都不胖lyh3 小时前
opencode在Ubuntu下无法复制
linux·运维·ubuntu