强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 7 章:PDF 处理

第 7 章:PDF 处理

将扫描 PDF 转换为可搜索、可复制的文档。

7.1 PDF OCR 概述

PDF OCR 三种场景
├── 1. 扫描件 PDF → 添加文本层 → 可搜索 PDF
├── 2. 图片 PDF → OCR → 可搜索 PDF
└── 3. 已有文本 PDF → 增强 → 更好文本层
输入类型处理方式输出
纯图片 PDF全页 OCR可搜索 PDF
混合 PDF仅 OCR 无文本层页面可搜索 PDF
已有文本跳过或增强可搜索 PDF
多页扫描逐页 OCR可搜索 PDF

7.2 基本 PDF OCR

7.2.1 Tesseract 直接输出 PDF

# 单图片 → 可搜索 PDF
tesseract image.png output pdf

# 多图片 → 多页 PDF
tesseract page1.png page2.png page3.png output pdf

# 指定语言
tesseract scan.png output pdf -l chi_sim+eng

# TIFF 多页 → PDF
tesseract multipage.tiff output pdf

7.2.2 Python 实现

import pytesseract
from PIL import Image

def image_to_searchable_pdf(image_path, output_path, lang='chi_sim+eng'):
    """将图片转换为可搜索 PDF"""
    img = Image.open(image_path)
    
    # 生成 PDF(含隐藏文本层)
    pdf_bytes = pytesseract.image_to_pdf_or_hocr(img, lang=lang, extension='pdf')
    
    with open(output_path, 'wb') as f:
        f.write(pdf_bytes)
    
    print(f"已生成: {output_path}")

# 使用
image_to_searchable_pdf('scan.png', 'output.pdf')

7.2.3 多页 PDF 生成

import pytesseract
from PIL import Image
import os

def images_to_pdf(image_dir, output_path, lang='chi_sim+eng'):
    """多张图片合并为一个可搜索 PDF"""
    images = []
    for filename in sorted(os.listdir(image_dir)):
        if filename.endswith(('.png', '.jpg', '.tif', '.tiff')):
            img = Image.open(os.path.join(image_dir, filename))
            if img.mode == 'RGBA':
                img = img.convert('RGB')
            images.append(img)
    
    if not images:
        print("未找到图片")
        return
    
    # 第一张图作为基础
    first_image = images[0]
    other_images = images[1:]
    
    # 逐页 OCR 并合并
    pdf_pages = []
    for img in images:
        pdf_bytes = pytesseract.image_to_pdf_or_hocr(img, lang=lang, extension='pdf')
        pdf_pages.append(pdf_bytes)
    
    # 使用 PyPDF2 合并(如果安装了的话)
    # 或者直接用第一张图生成多页 PDF
    first_image.save(
        output_path, 'PDF', 
        save_all=True, 
        append_images=other_images,
        resolution=300
    )
    
    print(f"已生成多页 PDF: {output_path} ({len(images)} 页)")

images_to_pdf('./scans', 'output.pdf')

7.3 OCRmyPDF(推荐方案)

7.3.1 安装

# pip 安装
pip install ocrmypdf

# Ubuntu 安装
sudo apt install ocrmypdf

# 安装中文语言支持
sudo apt install tesseract-ocr-chi-sim tesseract-ocr-chi-tra

7.3.2 基本使用

# 基本 OCR
ocrmypdf input.pdf output.pdf

# 指定语言
ocrmypdf -l chi_sim+eng input.pdf output.pdf

# 跳过已有文本的页面
ocrmypdf --skip-text input.pdf output.pdf

# 强制重新 OCR(即使已有文本)
ocrmypdf --force-ocr input.pdf output.pdf

# 优化 PDF 大小
ocrmypdf --optimize 3 --output-type pdf input.pdf output.pdf

# 添加书签
ocrmypdf --title "文档标题" --author "作者" input.pdf output.pdf

7.3.3 OCRmyPDF 选项详解

选项说明示例
-l语言-l chi_sim+eng
--skip-text跳过有文本的页面--skip-text
--force-ocr强制重新 OCR--force-ocr
--optimize优化级别 0-3--optimize 2
--output-type输出类型pdf / pdfa
--deskew自动校正倾斜--deskew
--rotate-pages自动旋转页面--rotate-pages
--clean清理扫描件--clean
--titlePDF 标题--title "标题"
--jobs并行进程数--jobs 4

7.3.4 批量 PDF OCR

#!/bin/bash
# batch_pdf_ocr.sh - 批量 PDF OCR

INPUT_DIR="./input_pdfs"
OUTPUT_DIR="./output_pdfs"
LANG="chi_sim+eng"

mkdir -p "$OUTPUT_DIR"

for pdf in "$INPUT_DIR"/*.pdf; do
    if [ -f "$pdf" ]; then
        filename=$(basename "$pdf")
        echo "Processing: $filename"
        ocrmypdf -l "$LANG" --deskew --clean --optimize 2 \
            "$pdf" "$OUTPUT_DIR/$filename"
    fi
done

echo "Done!"
import ocrmypdf
import os
from concurrent.futures import ProcessPoolExecutor

def ocr_single_pdf(args):
    """处理单个 PDF"""
    input_path, output_path, lang = args
    try:
        ocrmypdf.ocr(
            input_path, output_path,
            language=lang,
            deskew=True,
            clean=True,
            optimize=2,
            skip_text=True
        )
        return f"成功: {input_path}"
    except ocrmypdf.PriorOcrFoundError:
        return f"跳过(已有文本): {input_path}"
    except Exception as e:
        return f"失败: {input_path} - {e}"

def batch_pdf_ocr(input_dir, output_dir, lang='chi_sim+eng', workers=4):
    """批量 PDF OCR(并行)"""
    os.makedirs(output_dir, exist_ok=True)
    
    tasks = []
    for filename in os.listdir(input_dir):
        if filename.endswith('.pdf'):
            input_path = os.path.join(input_dir, filename)
            output_path = os.path.join(output_dir, filename)
            tasks.append((input_path, output_path, lang))
    
    print(f"待处理: {len(tasks)} 个文件")
    
    with ProcessPoolExecutor(max_workers=workers) as executor:
        results = list(executor.map(ocr_single_pdf, tasks))
    
    for result in results:
        print(result)

batch_pdf_ocr('./input_pdfs', './output_pdfs')

7.4 PDF 文本提取

7.4.1 从可搜索 PDF 提取文本

import fitz  # PyMuPDF

def extract_text_from_pdf(pdf_path):
    """从可搜索 PDF 提取文本"""
    doc = fitz.open(pdf_path)
    
    for page_num in range(len(doc)):
        page = doc[page_num]
        text = page.get_text()
        
        print(f"=== 第 {page_num + 1} 页 ===")
        print(text[:500])
        print()
    
    doc.close()

# pip install PyMuPDF
extract_text_from_pdf('output.pdf')

7.4.2 检查 PDF 是否已 OCR

import fitz

def is_pdf_searchable(pdf_path):
    """检查 PDF 是否包含文本层"""
    doc = fitz.open(pdf_path)
    
    for page_num in range(min(3, len(doc))):  # 检查前3页
        page = doc[page_num]
        text = page.get_text().strip()
        
        if len(text) > 10:  # 有足够文本
            doc.close()
            return True
    
    doc.close()
    return False

# 使用
if is_pdf_searchable('scan.pdf'):
    print("PDF 已包含文本层")
else:
    print("PDF 需要 OCR")

7.5 PDF 页面处理

7.5.1 PDF 页面提取为图片

import fitz

def pdf_to_images(pdf_path, output_dir, dpi=300):
    """将 PDF 页面转换为图片"""
    os.makedirs(output_dir, exist_ok=True)
    
    doc = fitz.open(pdf_path)
    zoom = dpi / 72  # 72 是 PDF 默认 DPI
    matrix = fitz.Matrix(zoom, zoom)
    
    for page_num in range(len(doc)):
        page = doc[page_num]
        pix = page.get_pixmap(matrix=matrix)
        
        output_path = os.path.join(output_dir, f'page_{page_num+1:03d}.png')
        pix.save(output_path)
        print(f"保存: {output_path}")
    
    doc.close()

7.5.2 选择性页面 OCR

import fitz
import pytesseract
from PIL import Image
import io

def selective_pdf_ocr(pdf_path, output_path, pages=None, lang='chi_sim+eng'):
    """选择性对指定页面进行 OCR"""
    doc = fitz.open(pdf_path)
    
    if pages is None:
        pages = range(len(doc))
    
    for page_num in pages:
        page = doc[page_num]
        text = page.get_text().strip()
        
        if len(text) < 10:  # 页面无文本,需要 OCR
            print(f"OCR 第 {page_num + 1} 页")
            
            # 提取页面图片
            pix = page.get_pixmap(dpi=300)
            img = Image.open(io.BytesIO(pix.tobytes('png')))
            
            # OCR
            ocr_text = pytesseract.image_to_string(img, lang=lang)
            
            # 可以将 OCR 文本保存或插入 PDF 文本层
            print(f"  识别文本: {ocr_text[:100]}...")
        else:
            print(f"跳过第 {page_num + 1} 页(已有文本)")
    
    doc.close()

selective_pdf_ocr('mixed.pdf', 'output.pdf', pages=[0, 2, 5])

7.6 PDF 后处理

7.6.1 PDF 压缩优化

# 使用 Ghostscript 压缩
gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/ebook \
   -dNOPAUSE -dQUIET -dBATCH \
   -sOutputFile=output_compressed.pdf input.pdf

# 使用 OCRmyPDF 优化
ocrmypdf --optimize 2 --output-type pdf input.pdf output.pdf

7.6.2 PDF 元数据添加

import fitz

def add_pdf_metadata(pdf_path, output_path, metadata):
    """添加 PDF 元数据"""
    doc = fitz.open(pdf_path)
    
    doc.set_metadata({
        'title': metadata.get('title', ''),
        'author': metadata.get('author', ''),
        'subject': metadata.get('subject', ''),
        'keywords': metadata.get('keywords', ''),
        'producer': 'Tesseract OCR'
    })
    
    doc.save(output_path)
    doc.close()

add_pdf_metadata('input.pdf', 'output.pdf', {
    'title': 'OCR 处理文档',
    'author': 'Tesseract',
    'keywords': 'OCR, 文档, 扫描'
})

7.7 业务场景

场景推荐方案配置
法律文档存档OCRmyPDF + 搜索--deskew --clean --optimize 2
合同数字化OCRmyPDF + 元数据--title --author
发票批量处理Python + 并行ProcessPoolExecutor
古籍扫描存档Tesseract + best 模型tessdata_best
学术论文归档OCRmyPDF + PDF/A--output-type pdfa

7.8 常见问题

问题原因解决方案
PDF 文件过大未压缩--optimize 2
OCR 速度慢单线程--jobs 4
中文乱码缺少语言包apt install tesseract-ocr-chi-sim
文本层不准确图片质量差先预处理图片
PDF/A 不兼容格式问题--output-type pdfa

7.9 本章小结

要点说明
推荐工具OCRmyPDF(功能全面、稳定)
基本命令ocrmypdf -l chi_sim+eng input.pdf output.pdf
批量处理Shell 脚本或 Python 并行
优化--optimize 2 --deskew --clean
文本提取PyMuPDF (fitz)

7.10 扩展阅读


上一章: 模型训练 | 下一章: Python 集成