🐍 第20课:PDF处理

—— PDF 的读与写

🏆 提取文字+合并+加水印
✅ Python验证通过

📌 本课目标

1️⃣ PyPDF2 读取 PDF

from PyPDF2 import PdfReader

# 打开 PDF
reader = PdfReader("document.pdf")

# 基本信息
print(f"页数: {len(reader.pages)}")
print(f"元数据: {reader.metadata}")

# 提取单页文字
page = reader.pages[0]  # 第一页(0索引)
text = page.extract_text()
print(text)

# 提取所有页文字
for i, page in enumerate(reader.pages):
    text = page.extract_text()
    if text.strip():
        print(f"--- 第{i+1}页 ---")
        print(text[:200])  # 预览前200字符

# 获取页面信息
page = reader.pages[0]
print(f"页面尺寸: {page.mediabox}")
print(f"宽度: {page.mediabox.width}")
print(f"高度: {page.mediabox.height}")
💡 extract_text() 的局限性:对扫描件 PDF(图片型)无效,需要 OCR 工具(如 Tesseract)。只对文本型 PDF 有效。

2️⃣ PDF 文字提取进阶

from PyPDF2 import PdfReader
import re

def extract_text_from_pdf(pdf_path: str) -> str:
    """提取PDF全部文字"""
    reader = PdfReader(pdf_path)
    all_text = []
    for page in reader.pages:
        text = page.extract_text()
        if text:
            all_text.append(text)
    return "\n".join(all_text)

def search_in_pdf(pdf_path: str, keyword: str) -> list[dict]:
    """在PDF中搜索关键词"""
    reader = PdfReader(pdf_path)
    results = []
    for i, page in enumerate(reader.pages):
        text = page.extract_text()
        if text and keyword in text:
            # 提取关键词上下文
            for m in re.finditer(
                rf'.{{0,40}}{re.escape(keyword)}.{{0,40}}', text
            ):
                results.append({
                    "page": i + 1,
                    "context": m.group().strip(),
                })
    return results

# 提取表格数据(简单方案)
def extract_table_like(pdf_path: str) -> list[list[str]]:
    """尝试从PDF提取表格数据"""
    reader = PdfReader(pdf_path)
    rows = []
    for page in reader.pages:
        text = page.extract_text()
        if not text:
            continue
        for line in text.split("\n"):
            if line.strip():
                # 按多空格分割
                cells = re.split(r'\s{2,}', line.strip())
                if len(cells) >= 2:
                    rows.append(cells)
    return rows

3️⃣ PDF 合并与拆分

from PyPDF2 import PdfReader, PdfWriter

# 合并多个 PDF
def merge_pdfs(pdf_paths: list[str], output_path: str):
    """合并多个PDF文件"""
    writer = PdfWriter()
    for path in pdf_paths:
        reader = PdfReader(path)
        for page in reader.pages:
            writer.add_page(page)

    with open(output_path, "wb") as f:
        writer.write(f)
    print(f"合并完成: {output_path} ({len(writer.pages)}页)")

# 拆分 PDF
def split_pdf(pdf_path: str, output_dir: str, pages_per_split: int = 1):
    """拆分PDF为多个小文件"""
    reader = PdfReader(pdf_path)
    total = len(reader.pages)
    from pathlib import Path
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    for start in range(0, total, pages_per_split):
        writer = PdfWriter()
        end = min(start + pages_per_split, total)
        for i in range(start, end):
            writer.add_page(reader.pages[i])

        out_file = output_path / f"part_{start//pages_per_split + 1}.pdf"
        with open(out_file, "wb") as f:
            writer.write(f)

    print(f"拆分完成: {total}页 → {(total + pages_per_split - 1) // pages_per_split}个文件")

# 提取指定页面
def extract_pages(pdf_path: str, page_numbers: list[int], output_path: str):
    """提取指定页面(0索引)"""
    reader = PdfReader(pdf_path)
    writer = PdfWriter()
    for n in page_numbers:
        if 0 <= n < len(reader.pages):
            writer.add_page(reader.pages[n])

    with open(output_path, "wb") as f:
        writer.write(f)

4️⃣ 添加水印

from PyPDF2 import PdfReader, PdfWriter
from reportlab.pdfgen import canvas  # pip install reportlab
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
import tempfile

# 第一步:创建水印 PDF
def create_watermark(text: str, output_path: str,
                      font_size: int = 60, opacity: float = 0.15):
    """生成水印PDF页面"""
    c = canvas.Canvas(output_path, pagesize=A4)
    width, height = A4

    # 设置半透明
    c.setFillAlpha(opacity)
    c.setFillGray(0.5)

    # 旋转45度
    c.saveState()
    c.translate(width / 2, height / 2)
    c.rotate(45)

    # 绘制水印文字
    c.setFont("Helvetica-Bold", font_size)
    c.drawCentredString(0, 0, text)

    # 重复绘制
    c.setFont("Helvetica-Bold", font_size // 2)
    for offset in [-150, 150]:
        c.drawCentredString(0, offset, text)

    c.restoreState()
    c.save()

# 第二步:将水印叠加到原PDF
def add_watermark(pdf_path: str, watermark_text: str,
                   output_path: str):
    """给PDF添加水印"""
    # 创建水印
    with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
        wm_path = tmp.name
    create_watermark(watermark_text, wm_path)

    # 叠加水印
    reader = PdfReader(pdf_path)
    wm_reader = PdfReader(wm_path)
    wm_page = wm_reader.pages[0]
    writer = PdfWriter()

    for page in reader.pages:
        page.merge_page(wm_page)  # 叠加水印
        writer.add_page(page)

    with open(output_path, "wb") as f:
        writer.write(f)

    # 清理临时文件
    import os
    os.unlink(wm_path)
    print(f"水印添加完成: {output_path}")

5️⃣ PDF 元数据与加密

from PyPDF2 import PdfReader, PdfWriter

# 修改元数据
reader = PdfReader("document.pdf")
writer = PdfWriter()
for page in reader.pages:
    writer.add_page(page)

writer.add_metadata({
    "/Title": "我的文档",
    "/Author": "张三",
    "/Subject": "自动化课程",
    "/Creator": "Python PyPDF2",
})

# 加密
writer.encrypt(user_password="123456", owner_password="admin")

with open("encrypted.pdf", "wb") as f:
    writer.write(f)

# 解密读取
reader = PdfReader("encrypted.pdf")
if reader.is_encrypted:
    reader.decrypt("123456")
    text = reader.pages[0].extract_text()

6️⃣ 验证脚本

#!/usr/bin/env python3
"""第20课 PDF处理验证"""
from PyPDF2 import PdfReader, PdfWriter
import tempfile
import os

def create_test_pdf(path, num_pages=3):
    """创建测试PDF"""
    from reportlab.pdfgen import canvas
    c = canvas.Canvas(path)
    for i in range(num_pages):
        c.setFont("Helvetica", 12)
        c.drawString(100, 700, f"Page {i+1} - Test Content")
        c.showPage()
    c.save()

def test_pdf_create_read():
    """PDF创建与读取测试"""
    with tempfile.TemporaryDirectory() as tmpdir:
        pdf_path = os.path.join(tmpdir, "test.pdf")
        create_test_pdf(pdf_path)

        reader = PdfReader(pdf_path)
        assert len(reader.pages) == 3
        text = reader.pages[0].extract_text()
        assert "Page 1" in text
    print("✅ PDF创建与读取测试通过")

def test_pdf_merge():
    """PDF合并测试"""
    with tempfile.TemporaryDirectory() as tmpdir:
        pdf1 = os.path.join(tmpdir, "part1.pdf")
        pdf2 = os.path.join(tmpdir, "part2.pdf")
        merged = os.path.join(tmpdir, "merged.pdf")

        create_test_pdf(pdf1, 2)
        create_test_pdf(pdf2, 3)

        writer = PdfWriter()
        for p in [pdf1, pdf2]:
            for page in PdfReader(p).pages:
                writer.add_page(page)

        with open(merged, "wb") as f:
            writer.write(f)

        reader = PdfReader(merged)
        assert len(reader.pages) == 5
    print("✅ PDF合并测试通过")

def test_pdf_split():
    """PDF拆分测试"""
    with tempfile.TemporaryDirectory() as tmpdir:
        src = os.path.join(tmpdir, "source.pdf")
        create_test_pdf(src, 4)

        reader = PdfReader(src)
        # 提取第1-2页
        writer = PdfWriter()
        writer.add_page(reader.pages[0])
        writer.add_page(reader.pages[1])
        out = os.path.join(tmpdir, "split.pdf")
        with open(out, "wb") as f:
            writer.write(f)

        assert len(PdfReader(out).pages) == 2
    print("✅ PDF拆分测试通过")

def test_pdf_watermark():
    """水印测试"""
    with tempfile.TemporaryDirectory() as tmpdir:
        src = os.path.join(tmpdir, "source.pdf")
        wm = os.path.join(tmpdir, "watermark.pdf")
        out = os.path.join(tmpdir, "output.pdf")

        create_test_pdf(src, 2)

        # 创建水印
        from reportlab.pdfgen import canvas
        from reportlab.lib.pagesizes import A4
        c = canvas.Canvas(wm, pagesize=A4)
        c.setFillAlpha(0.15)
        c.setFont("Helvetica-Bold", 60)
        c.drawCentredString(297, 420, "WATERMARK")
        c.save()

        # 叠加水印
        reader = PdfReader(src)
        wm_page = PdfReader(wm).pages[0]
        writer = PdfWriter()
        for page in reader.pages:
            page.merge_page(wm_page)
            writer.add_page(page)
        with open(out, "wb") as f:
            writer.write(f)

        result = PdfReader(out)
        assert len(result.pages) == 2
    print("✅ 水印测试通过")

if __name__ == "__main__":
    test_pdf_create_read()
    test_pdf_merge()
    test_pdf_split()
    test_pdf_watermark()
    print("\n🎉 第20课全部验证通过!")
✅ PDF创建与读取测试通过 ✅ PDF合并测试通过 ✅ PDF拆分测试通过 ✅ 水印测试通过 🎉 第20课全部验证通过!

🔑 本课要点

  1. PyPDF2——PDF 读写首选,纯 Python,无需系统依赖
  2. reportlab——创建 PDF 和水印的利器
  3. extract_text()——只对文本型 PDF 有效,扫描件需 OCR
  4. merge_page()——叠加页面,水印原理就是这个
  5. encrypt/decrypt——简单加密,重要文档请用专业工具