1. 什么是 pdfplumber? #

2. 前置知识 #

2.1 文字型 PDF 与扫描版 PDF #

2.2 代码里的两个核心对象 #

3. 环境准备 #

3.1 安装 #

# 说明:升级 pip 到最新版本
py -m pip install --upgrade pip

# 说明:安装 pdfplumber 主库
py -m pip install pdfplumber

# 说明:安装 fpdf2,用于教程示例自动生成测试 PDF
py -m pip install fpdf2

3.2 验证安装 #

# -*- coding: utf-8 -*-
# 说明:导入 Path,用于处理文件路径
from pathlib import Path

# 说明:导入 FPDF,用于生成测试 PDF
from fpdf import FPDF

# 说明:导入 pdfplumber,用于读取 PDF
import pdfplumber

# 说明:定义 demo_output 目录路径
demo_dir = Path("demo_output")

# 说明:创建目录,已存在则不报错
demo_dir.mkdir(exist_ok=True)

# 说明:测试 PDF 文件路径
pdf_path = demo_dir / "verify_install.pdf"

# 说明:创建 FPDF 对象
pdf = FPDF()

# 说明:添加一页
pdf.add_page()

# 说明:设置字体为 Helvetica,14 号
pdf.set_font("Helvetica", size=14)

# 说明:写入一行文字
pdf.cell(text="pdfplumber install ok")

# 说明:保存 PDF 到文件
pdf.output(str(pdf_path))

# 说明:用 with 打开 PDF 并读取第一页文字
with pdfplumber.open(pdf_path) as doc:
    text = doc.pages[0].extract_text()

# 说明:打印 pdfplumber 版本号
print("pdfplumber 版本:", pdfplumber.__version__)

# 说明:打印读取到的文字
print("读取到的文字:", text)

4. 打开 PDF 与提取文本 #

# -*- coding: utf-8 -*-
# 说明:导入 Path 处理路径
from pathlib import Path

# 说明:导入 FPDF 生成示例 PDF
from fpdf import FPDF

# 说明:导入 pdfplumber
import pdfplumber

# 说明:示例 PDF 路径
PDF_PATH = Path("demo_output") / "sample.pdf"

# 说明:输出 txt 路径
TXT_PATH = Path("demo_output") / "sample.txt"


def create_sample_pdf() -> None:
    """若示例 PDF 不存在则自动创建。"""
    # 说明:确保父目录存在
    PDF_PATH.parent.mkdir(exist_ok=True)
    # 说明:文件已存在则跳过
    if PDF_PATH.exists():
        return
    # 说明:创建 PDF 对象
    pdf = FPDF()
    # 说明:添加第一页
    pdf.add_page()
    pdf.set_font("Helvetica", size=14)
    pdf.cell(text="Invoice #1001")
    pdf.ln(8)
    pdf.set_font("Helvetica", size=11)
    pdf.multi_cell(w=0, h=7, text="Customer: Zhang San\nAmount: 1280.00")
    # 说明:添加第二页
    pdf.add_page()
    pdf.set_font("Helvetica", size=12)
    pdf.cell(text="Page 2 content")
    # 说明:保存 PDF
    pdf.output(str(PDF_PATH))


# 说明:确保示例 PDF 存在
create_sample_pdf()

# 说明:存放各页文字的列表
parts = []

# 说明:打开 PDF 并逐页提取
with pdfplumber.open(PDF_PATH) as pdf:
    print("总页数:", len(pdf.pages))
    for page in pdf.pages:
        text = page.extract_text() or ""
        parts.append(f"\n===== 第 {page.page_number} 页 =====\n{text}")
        print(f"第 {page.page_number} 页预览:{text[:30]!r}")

# 说明:写入 txt 文件
TXT_PATH.write_text("".join(parts), encoding="utf-8")

# 说明:打印保存路径
print("已保存:", TXT_PATH.resolve())

5. 提取表格 #

5.1 提取单张表格 #

# -*- coding: utf-8 -*-
# 说明:导入 Path
from pathlib import Path

# 说明:导入 FPDF
from fpdf import FPDF

# 说明:导入 pdfplumber
import pdfplumber

# 说明:表格示例 PDF 路径
PDF_PATH = Path("demo_output") / "table.pdf"


def create_table_pdf() -> None:
    # 说明:确保目录存在
    PDF_PATH.parent.mkdir(exist_ok=True)
    if PDF_PATH.exists():
        return
    pdf = FPDF()
    pdf.add_page()
    pdf.set_font("Helvetica", size=11)
    col_w, row_h, x0, y0 = 45, 10, 20, 30
    rows = [
        ["Name", "Age", "City"],
        ["Alice", "25", "Beijing"],
        ["Bob", "30", "Shanghai"],
    ]
    for ri, row in enumerate(rows):
        for ci, cell in enumerate(row):
            pdf.set_xy(x0 + ci * col_w, y0 + ri * row_h)
            pdf.cell(col_w, row_h, cell, border=1)
    pdf.output(str(PDF_PATH))


# 说明:生成带边框的示例 PDF
create_table_pdf()

# 说明:有边框表格的检测策略:按线条识别行列
settings = {"vertical_strategy": "lines", "horizontal_strategy": "lines"}

# 说明:打开 PDF 并提取第一张表
with pdfplumber.open(PDF_PATH) as pdf:
    table = pdf.pages[0].extract_table(table_settings=settings)

# 说明:逐行打印表格内容
for row in table or []:
    print(row)

5.2 一页多表:find_tables + crop #

# -*- coding: utf-8 -*-
# 说明:导入 Path
from pathlib import Path

# 说明:导入 FPDF
from fpdf import FPDF

# 说明:导入 pdfplumber
import pdfplumber

# 说明:多表示例 PDF 路径
PDF_PATH = Path("demo_output") / "multi_table.pdf"


def draw_table(pdf, x, y, headers, rows, col_w=40, row_h=10):
    # 说明:画表头
    for i, h in enumerate(headers):
        pdf.set_xy(x + i * col_w, y)
        pdf.cell(col_w, row_h, h, border=1)
    # 说明:画数据行
    for ri, row in enumerate(rows):
        for ci, cell in enumerate(row):
            pdf.set_xy(x + ci * col_w, y + (ri + 1) * row_h)
            pdf.cell(col_w, row_h, cell, border=1)


def create_sample_pdf() -> None:
    PDF_PATH.parent.mkdir(exist_ok=True)
    if PDF_PATH.exists():
        return
    pdf = FPDF()
    pdf.add_page()
    pdf.set_font("Helvetica", size=11)
    draw_table(pdf, 20, 30, ["Product", "Price"], [["Apple", "5"]])
    draw_table(pdf, 20, 80, ["ID", "Status"], [["001", "OK"], ["002", "Fail"]])
    pdf.output(str(PDF_PATH))


# 说明:生成含两张表的 PDF
create_sample_pdf()

# 说明:打开 PDF 并提取表格
with pdfplumber.open(PDF_PATH) as pdf:
    page = pdf.pages[0]
    tables = page.extract_tables()
    print(f"extract_tables 共 {len(tables)} 张表")
    found = page.find_tables()
    if len(found) >= 2:
        table = page.crop(found[1].bbox).extract_table()
        print("第二张表:", table)

6. 表格导出 CSV #

# -*- coding: utf-8 -*-
# 说明:导入 csv 模块
import csv

# 说明:导入 Path
from pathlib import Path

# 说明:导入 FPDF
from fpdf import FPDF

# 说明:导入 pdfplumber
import pdfplumber

# 说明:源 PDF 路径
PDF_PATH = Path("demo_output") / "table.pdf"

# 说明:输出 CSV 路径
CSV_PATH = Path("demo_output") / "table.csv"


def create_table_pdf() -> None:
    PDF_PATH.parent.mkdir(exist_ok=True)
    if PDF_PATH.exists():
        return
    pdf = FPDF()
    pdf.add_page()
    pdf.set_font("Helvetica", size=11)
    col_w, row_h, x0, y0 = 45, 10, 20, 30
    rows = [["Name", "Age"], ["Alice", "25"], ["Bob", "30"]]
    for ri, row in enumerate(rows):
        for ci, cell in enumerate(row):
            pdf.set_xy(x0 + ci * col_w, y0 + ri * row_h)
            pdf.cell(col_w, row_h, cell, border=1)
    pdf.output(str(PDF_PATH))


# 说明:确保 PDF 存在
create_table_pdf()

# 说明:从 PDF 提取表格
with pdfplumber.open(PDF_PATH) as pdf:
    table = pdf.pages[0].extract_table()

# 说明:未提取到表格则报错
if not table:
    raise RuntimeError("未提取到表格")

# 说明:写入 CSV,None 单元格转为空字符串
with CSV_PATH.open("w", newline="", encoding="utf-8-sig") as f:
    writer = csv.writer(f)
    for row in table:
        writer.writerow([cell if cell is not None else "" for cell in row])

# 说明:打印保存路径
print("CSV 已保存:", CSV_PATH.resolve())

7. 常见问题 #

7.1 extract_text 返回 None #

7.2 表格行列错乱 #

7.3 中文与文件路径 #

8. API 速查 #

操作 写法
打开文件 with pdfplumber.open("a.pdf") as pdf:
总页数 len(pdf.pages)
取某一页 page = pdf.pages[0]
提取全文 page.extract_text()
提取一张表 page.extract_table()
提取所有表 page.extract_tables()
定位表格 page.find_tables() → .bbox
裁剪区域 page.crop((x0, top, x1, bottom))
调表格参数 page.extract_table(table_settings={...})