使用 Python 中的 PDFMiner 从 PDF 中提取文本

用 Python 掌握 PDF 文本提取

目录

PDFMiner.six 是一个强大的 Python 库,用于从 PDF 文档中提取文本、元数据和布局信息。

与简单的 PDF 读取器不同,它提供对 PDF 结构的深入分析,并能有效处理复杂的布局。

从PDF到markdown的文本提取 - IPAD可视化

什么是 PDFMiner,为什么使用它?

PDFMiner 是一个纯 Python 库,专为从 PDF 文档中提取和分析文本而设计。.six 版本是目前积极维护的分支,支持 Python 3.x,而原始的 PDFMiner 项目已不再更新。

主要功能:

  • 纯 Python 实现(无需外部依赖)
  • 详细的布局分析和文本定位
  • 字体和字符编码检测
  • 支持加密的 PDF
  • 包含命令行工具
  • 可扩展架构,适用于自定义处理

当您需要对文本提取进行精确控制、需要保留布局信息或处理复杂的多栏文档时,PDFMiner 非常有用。尽管它可能比某些替代方案更慢,但其准确性和详细的分析能力使其成为文档处理流水线的首选。对于反向工作流程,您可能还对 使用 Python 生成 PDF 感兴趣。

安装和设置

使用 pip 安装 PDFMiner.six:

pip install pdfminer.six

对于虚拟环境(推荐):

python -m venv venv
source venv/bin/activate  # 在 Windows 上:venv\Scripts\activate
pip install pdfminer.six

如果您是 Python 包管理的新手,请查看我们的 Python 快速参考,以了解 pip 和虚拟环境的更多细节。

验证安装:

pdf2txt.py --version

该库包含多个命令行工具:

  • pdf2txt.py - 从 PDF 中提取文本
  • dumppdf.py - 转储 PDF 内部结构
  • latin2ascii.py - 将拉丁字符转换为 ASCII

这些工具与 如 Poppler 的 PDF 操作工具 互补,这些工具提供额外的功能,如页面提取和格式转换。

基本文本提取

简单文本提取

从 PDF 中提取文本的最简单方法:

from pdfminer.high_level import extract_text

# 从 PDF 中提取所有文本
text = extract_text('document.pdf')
print(text)

这个高级 API 处理大多数常见用例,并将整个文档作为单个字符串返回。

从特定页面提取文本

要从特定页面提取文本:

from pdfminer.high_level import extract_text

# 从页面 2-5 提取文本(0 索引)
text = extract_text('document.pdf', page_numbers=[1, 2, 3, 4])
print(text)

这对于大型文档非常有用,当您只需要某些部分时,可以显著提高性能。

通过页面迭代提取文本

处理单个页面时:

from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextContainer

for page_layout in extract_pages('document.pdf'):
    for element in page_layout:
        if isinstance(element, LTTextContainer):
            print(element.get_text())

这种方法为您提供了更多控制如何处理每一页,适用于页面结构各不相同的文档。

高级布局分析

了解 LAParams

LAParams(布局分析参数)控制 PDFMiner 如何解释文档布局。理解 PDFMiner 与简单库之间的区别在这里非常重要 - PDFMiner 实际上分析文本元素之间的空间关系。

from pdfminer.high_level import extract_text
from pdfminer.layout import LAParams

# 创建自定义 LAParams
laparams = LAParams(
    line_overlap=0.5,      # 文本行的最小重叠
    char_margin=2.0,       # 字符边距
    line_margin=0.5,       # 行边距
    word_margin=0.1,       # 单词间距
    boxes_flow=0.5,        # 文本框流阈值
    detect_vertical=True,  # 检测垂直文本
    all_texts=False        # 仅提取文本框中的文本
)

text = extract_text('document.pdf', laparams=laparams)

参数解释:

  • line_overlap:行必须垂直重叠多少才能被视为同一行(0.0-1.0)
  • char_margin:同一单词中字符之间的最大间距(字符宽度的倍数)
  • line_margin:同一段落中行之间的最大间距
  • word_margin:分隔单词的间距阈值
  • boxes_flow:文本框流方向的阈值
  • detect_vertical:启用垂直文本检测(常见于亚洲语言)

提取布局信息

获取详细的定位和字体信息:

from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextBox, LTTextLine, LTChar

for page_layout in extract_pages('document.pdf'):
    for element in page_layout:
        if isinstance(element, LTTextBox):
            # 获取边界框坐标
            x0, y0, x1, y1 = element.bbox
            print(f"文本位于 ({x0}, {y0}):{element.get_text()}")
            
            # 遍历行
            for text_line in element:
                if isinstance(text_line, LTTextLine):
                    # 获取字符级别的细节
                    for char in text_line:
                        if isinstance(char, LTChar):
                            print(f"字符:{char.get_text()}, "
                                  f"字体:{char.fontname}, "
                                  f"大小:{char.height}")

这种级别的详细信息对于文档分析、表单提取或需要了解文档结构的编程处理非常有价值。

处理不同的 PDF 类型

加密的 PDF

PDFMiner 可以处理受密码保护的 PDF:

from pdfminer.high_level import extract_text

# 从受密码保护的 PDF 提取
text = extract_text('encrypted.pdf', password='your_password')

请注意,PDFMiner 只能从 PDF 中提取文本,不能绕过防止文本提取的 PDF 级别安全限制。

多栏文档

对于具有多栏的文档,调整 LAParams:

from pdfminer.high_level import extract_text
from pdfminer.layout import LAParams

# 优化多栏布局
laparams = LAParams(
    detect_vertical=False,
    line_margin=0.3,
    word_margin=0.1,
    boxes_flow=0.3  # 更低值有助于更好地检测列
)

text = extract_text('multi_column.pdf', laparams=laparams)

boxes_flow 参数对于多栏文档尤为重要 - 更低的值有助于 PDFMiner 区分不同的列。

非英语和 Unicode 文本

PDFMiner 处理 Unicode 很好,但请确保使用正确的编码:

from pdfminer.high_level import extract_text

# 使用 Unicode 支持提取文本
text = extract_text('multilingual.pdf', codec='utf-8')

# 以 UTF-8 编码保存到文件
with open('output.txt', 'w', encoding='utf-8') as f:
    f.write(text)

处理扫描的 PDF

PDFMiner 无法直接从扫描的 PDF(图像)中提取文本。这些需要 OCR(光学字符识别)。不过,您可以将 PDFMiner 与 OCR 工具集成。

以下是检测 PDF 是否为扫描并需要 OCR 的方法:

from pdfminer.high_level import extract_text
from pdfminer.high_level import extract_pages
from pdfminer.layout import LTFigure, LTImage

def is_scanned_pdf(pdf_path):
    """检查 PDF 是否为扫描(主要是图像)"""
    text_count = 0
    image_count = 0
    
    for page_layout in extract_pages(pdf_path):
        for element in page_layout:
            if isinstance(element, (LTFigure, LTImage)):
                image_count += 1
            elif hasattr(element, 'get_text'):
                if element.get_text().strip():
                    text_count += 1
    
    # 如果主要是图像且文本很少,可能是扫描的
    return image_count > text_count * 2

if is_scanned_pdf('document.pdf'):
    print("此 PDF 看起来是扫描的 - 使用 OCR")
else:
    text = extract_text('document.pdf')
    print(text)

对于扫描的 PDF,可以考虑与 Tesseract OCR 集成,或者先使用 工具从 PDF 中提取图像,然后对这些图像进行 OCR。

命令行使用

PDFMiner 包含强大的命令行工具:

使用命令行工具提取文本

# 提取文本到标准输出
pdf2txt.py document.pdf

# 保存到文件
pdf2txt.py -o output.txt document.pdf

# 提取特定页面
pdf2txt.py -p 1,2,3 document.pdf

# 以 HTML 格式提取
pdf2txt.py -t html -o output.html document.pdf

高级选项

# 自定义布局参数
pdf2txt.py -L 0.3 -W 0.1 document.pdf

# 提取详细布局(XML)
pdf2txt.py -t xml -o layout.xml document.pdf

# 设置加密 PDF 的密码
pdf2txt.py -P mypassword encrypted.pdf

这些命令行工具非常适合快速测试、shell 脚本和集成到自动化工作流程中。

性能优化

处理大型 PDF

对于大型文档,考虑以下优化策略:

from pdfminer.high_level import extract_pages
from pdfminer.layout import LAParams

# 仅处理所需页面
def extract_page_range(pdf_path, start_page, end_page):
    text_content = []
    for i, page_layout in enumerate(extract_pages(pdf_path)):
        if i < start_page:
            continue
        if i >= end_page:
            break
        text_content.append(page_layout)
    return text_content

# 禁用布局分析以提高速度
from pdfminer.high_level import extract_text
text = extract_text('large.pdf', laparams=None)  # 快得多

批量处理

为了高效处理多个 PDF:

from multiprocessing import Pool
from pdfminer.high_level import extract_text
import os

def process_pdf(pdf_path):
    """处理单个 PDF 文件"""
    try:
        text = extract_text(pdf_path)
        output_path = pdf_path.replace('.pdf', '.txt')
        with open(output_path, 'w', encoding='utf-8') as f:
            f.write(text)
        return f"已处理: {pdf_path}"
    except Exception as e:
        return f"处理 {pdf_path} 时出错: {str(e)}"

# 并行处理 PDF
def batch_process_pdfs(pdf_directory, num_workers=4):
    pdf_files = [os.path.join(pdf_directory, f) 
                 for f in os.listdir(pdf_directory) 
                 if f.endswith('.pdf')]
    
    with Pool(num_workers) as pool:
        results = pool.map(process_pdf, pdf_files)
    
    for result in results:
        print(result)

# 使用方法
batch_process_pdfs('/path/to/pdfs', num_workers=4)

常见问题和解决方案

问题:文本顺序错误

问题: 提取的文本混乱或顺序错误。

解决方案: 调整 LAParams,特别是 boxes_flow

from pdfminer.layout import LAParams
laparams = LAParams(boxes_flow=0.3)  # 尝试不同值
text = extract_text('document.pdf', laparams=laparams)

问题:单词之间缺少空格

问题: 单词连在一起,没有空格。

解决方案: 增加 word_margin

laparams = LAParams(word_margin=0.2)  # 从默认值 0.1 增加
text = extract_text('document.pdf', laparams=laparams)

问题:编码错误

问题: 出现奇怪字符或编码错误。

解决方案: 显式指定编码:

text = extract_text('document.pdf', codec='utf-8')

问题:处理大型 PDF 时出现内存错误

问题: 处理大型文件时出现内存错误。

解决方案: 按页面处理:

def extract_text_chunked(pdf_path, chunk_size=10):
    """分块提取文本以减少内存使用"""
    all_text = []
    page_count = 0
    
    for page_layout in extract_pages(pdf_path):
        page_text = []
        for element in page_layout:
            if hasattr(element, 'get_text'):
                page_text.append(element.get_text())
        
        all_text.append(''.join(page_text))
        page_count += 1
        
        # 分块处理
        if page_count % chunk_size == 0:
            yield ''.join(all_text)
            all_text = []
    
    # 输出剩余文本
    if all_text:
        yield ''.join(all_text)

与替代方案的比较

了解何时使用 PDFMiner 而不是其他库非常重要:

PDFMiner 与 PyPDF2

PyPDF2 更简单、更快,但准确性较低:

  • 使用 PyPDF2 的情况:简单 PDF、快速提取、合并/拆分 PDF
  • 使用 PDFMiner 的情况:复杂布局、精确文本定位、详细分析

PDFMiner 与 pdfplumber

pdfplumber 在 PDFMiner 的基础上构建,提供了更高级的 API:

  • 使用 pdfplumber 的情况:表格提取、更简单的 API、快速原型设计
  • 使用 PDFMiner 的情况:最大控制、自定义处理、生产系统

PDFMiner 与 PyMuPDF(fitz)

PyMuPDF 显著更快,但有 C 依赖项:

  • 使用 PyMuPDF 的情况:对性能要求高的应用、大规模处理
  • 使用 PDFMiner 的情况:纯 Python 要求、详细布局分析

实用示例:提取并分析文档

以下是一个完整示例,提取文本并提供文档统计信息:

from pdfminer.high_level import extract_pages, extract_text
from pdfminer.layout import LTTextBox, LTChar
from collections import Counter
import re

def analyze_pdf(pdf_path):
    """提取文本并提供文档分析"""
    
    # 提取全文
    full_text = extract_text(pdf_path)
    
    # 统计信息
    stats = {
        'total_chars': len(full_text),
        'total_words': len(full_text.split()),
        'total_lines': full_text.count('\n'),
        'fonts': Counter(),
        'font_sizes': Counter(),
        'pages': 0
    }
    
    # 详细分析
    for page_layout in extract_pages(pdf_path):
        stats['pages'] += 1
        
        for element in page_layout:
            if isinstance(element, LTTextBox):
                for line in element:
                    for char in line:
                        if isinstance(char, LTChar):
                            stats['fonts'][char.fontname] += 1
                            stats['font_sizes'][round(char.height, 1)] += 1
    
    return {
        'text': full_text,
        'stats': stats,
        'most_common_font': stats['fonts'].most_common(1)[0] if stats['fonts'] else None,
        'most_common_size': stats['font_sizes'].most_common(1)[0] if stats['font_sizes'] else None
    }

# 使用方法
result = analyze_pdf('document.pdf')
print(f"页数: {result['stats']['pages']}")
print(f"单词数: {result['stats']['total_words']}")
print(f"主要字体: {result['most_common_font']}")
print(f"主要字号: {result['most_common_size']}")

与文档处理流水线的集成

PDFMiner 在较大的文档处理工作流程中表现良好。例如,在构建 RAG(检索增强生成)系统或文档管理解决方案时,可以将其与其他 Python 工具结合,形成完整的流水线。

一旦从 PDF 中提取了文本,通常需要将其转换为其他格式。您可以 使用 Python 库将 HTML 内容转换为 Markdown 或甚至利用 使用 Ollama 的 LLM 转换 进行智能文档转换。这些技术在 PDF 提取产生类似 HTML 的结构化文本并需要清理和重新格式化时特别有用。

对于全面的文档转换流水线,您可能还需要处理 Word 文档到 Markdown 的转换,创建一个统一的工作流程,处理多种文档格式并转换为通用输出格式。

最佳实践

  1. 复杂文档始终使用 LAParams - 默认设置适用于简单文档,但调整 LAParams 会显著改善复杂布局的结果。

  2. 先用示例页面进行测试 - 在处理大批量文件之前,先在代表性样本上测试您的提取设置。

  3. 优雅地处理异常 - PDF 文件可能损坏或格式错误。始终将提取代码包裹在 try-except 块中。

  4. 缓存提取的文本 - 对于重复处理,缓存提取的文本以避免重新处理。

  5. 验证提取的文本 - 实施检查以验证提取质量(例如,最小文本长度、预期关键词)。

  6. 针对特定用例考虑替代方案 - 虽然 PDFMiner 功能强大,但有时专门的工具(如用于表格的 tabula-py)更合适。

  7. 保持 PDFMiner 更新 - .six 分支正在积极维护。保持更新以获得错误修复和改进。

  8. 为代码编写良好的文档 - 当分享 PDF 提取脚本时,使用适当的 Markdown 代码块 与语法高亮,以提高可读性。

结论

PDFMiner.six 是 Python 开发人员处理 PDF 文档时必不可少的工具。其纯 Python 实现、详细的布局分析和可扩展架构使其非常适合生产文档处理系统。尽管它可能比简单库的学习曲线更陡峭,但它在复杂 PDF 提取任务中提供的精确性和控制力是无与伦比的。

无论您是在构建文档管理系统、分析科学论文还是从机器学习流水线中提取数据,PDFMiner 都为 Python 提供了可靠的 PDF 文本提取基础。

相关资源

本网站的相关文章

外部参考资料