1. 什么是 pdfplumber? #
- pdfplumber 是 Python 中常用的 PDF 解析库(当前主流版本 0.11.x),主要用来提取文字和表格。
- 它基于
pdfminer.six 开发,语法比 pdfminer 更简单,表格识别能力更强。
- 适合处理电脑导出的文字型 PDF(Word、Excel、浏览器打印、报表导出等)。
- 不适合扫描版 PDF(本质是图片,需 OCR,本教程不涉及)。
- 典型用途:批量读报告文字、把 PDF 表格转成 Python 列表或 CSV。
- 需要同时提取文字和表格时优先选 pdfplumber;只需合并/拆分 PDF 可考虑 pypdf。
2. 前置知识 #
- 正式写代码前,先确认 PDF 是文字型还是扫描版,能避免大部分「读不到内容」的问题。
- 本教程只覆盖文字型 PDF,不涉及 OCR。
2.1 文字型 PDF 与扫描版 PDF #
- 文字型 PDF:软件直接导出,PDF 内保存了文字对象,pdfplumber 可以直接读取。
- 扫描版 PDF:由扫描或拍照生成,页面上只有图片,pdfplumber 读不到文字。
- 快速判断:用阅读器打开 PDF,尝试选中并复制文字——能复制就是文字型,不能复制多半是扫描版。
2.2 代码里的两个核心对象 #
- PDF 对象:整个
.pdf 文件,通过 pdfplumber.open() 打开。
- Page 对象:每一页,用
pdf.pages[0] 访问(索引从 0 开始)。
- 页码:
page.page_number 从 1 开始,与列表索引不同。
3. 环境准备 #
- 安装 pdfplumber 即可使用;教程示例用 fpdf2 自动生成测试 PDF,无需自备样例文件。
- 推荐 Python 3.9 及以上版本(pdfplumber 0.11.x 支持 Python 3.8+)。
3.1 安装 #
py -m pip install --upgrade pip
py -m pip install pdfplumber
py -m pip install fpdf2
- 下载慢时可加镜像:
py -m pip install pdfplumber fpdf2 -i https://pypi.tuna.tsinghua.edu.cn/simple
3.2 验证安装 #
from pathlib import Path
from fpdf import FPDF
import pdfplumber
demo_dir = Path("demo_output")
demo_dir.mkdir(exist_ok=True)
pdf_path = demo_dir / "verify_install.pdf"
pdf = FPDF()
pdf.add_page()
pdf.set_font("Helvetica", size=14)
pdf.cell(text="pdfplumber install ok")
pdf.output(str(pdf_path))
with pdfplumber.open(pdf_path) as doc:
text = doc.pages[0].extract_text()
print("pdfplumber 版本:", pdfplumber.__version__)
print("读取到的文字:", text)
4. 打开 PDF 与提取文本 #
- 始终用
with pdfplumber.open(路径) as pdf: 打开文件,退出时自动关闭。
len(pdf.pages) 获取总页数,for page in pdf.pages 逐页处理。
page.extract_text() 提取本页全部文字,可能返回 None,建议写 or "" 兜底。
- 多页文字拼接后可写入
.txt 文件保存。
from pathlib import Path
from fpdf import FPDF
import pdfplumber
PDF_PATH = Path("demo_output") / "sample.pdf"
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 = 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.output(str(PDF_PATH))
create_sample_pdf()
parts = []
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_PATH.write_text("".join(parts), encoding="utf-8")
print("已保存:", TXT_PATH.resolve())
5. 提取表格 #
extract_table() 提取本页面积最大的一张表,返回 list[list](行 → 列)。
extract_tables() 提取本页所有表格,返回 list[list[list]]。
- 一页有多张表且
extract_table() 拿错时:用 find_tables() 拿 bbox,再 crop() 后提取。
- 单元格可能为
None,写入 CSV 前转为空字符串。
- 识别不准时传
table_settings,常用 vertical_strategy 和 horizontal_strategy(取值 "lines" 或 "text")。
5.1 提取单张表格 #
from pathlib import Path
from fpdf import FPDF
import pdfplumber
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))
create_table_pdf()
settings = {"vertical_strategy": "lines", "horizontal_strategy": "lines"}
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 #
find_tables() 返回所有表格对象,每个有 .bbox 属性。
bbox 格式为 (x0, top, x1, bottom),原点在页面左上角。
- 用
page.crop(bbox) 裁剪后再 extract_table(),可精确提取目标表。
from pathlib import Path
from fpdf import FPDF
import pdfplumber
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))
create_sample_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 #
- 提取表格后保存为 CSV,是最常见的下游操作。
- 使用
encoding="utf-8-sig",Excel 双击打开中文 CSV 不易乱码。
- 读取真实业务 PDF 时,把
PDF_PATH 换成你的文件路径即可,其余逻辑相同。
import csv
from pathlib import Path
from fpdf import FPDF
import pdfplumber
PDF_PATH = Path("demo_output") / "table.pdf"
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))
create_table_pdf()
with pdfplumber.open(PDF_PATH) as pdf:
table = pdf.pages[0].extract_table()
if not table:
raise RuntimeError("未提取到表格")
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 #
- 原因:该页没有文字对象(空白页、纯图片页或扫描件)。
- 处理:写
page.extract_text() or "" 避免后续报错。
- 若所有页都是 None,PDF 可能是扫描版,需换 OCR 方案。
7.2 表格行列错乱 #
- 先确认是文字型 PDF,且表格在页面上可见。
- 有边框的表:设
vertical_strategy="lines" 和 horizontal_strategy="lines"。
- 无边框的表:两个都设
"text"。
- 一页多表:用 §5.2 的
find_tables + crop 单独提取。
7.3 中文与文件路径 #
- pdfplumber 读取中文 PDF 没问题,前提是 PDF 内嵌了文字(文字型 PDF)。
- Windows 路径推荐用
pathlib.Path,或字符串前加 r"D:\data\file.pdf"。
8. API 速查 #
- 下面汇总 pdfplumber 0.11.x 日常最常用的写法。
| 操作 |
写法 |
| 打开文件 |
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={...}) |
table_settings 常用键:vertical_strategy、horizontal_strategy,取值 "lines" 或 "text"。
- 核心流程:
open → 选 page → extract_text() 或 extract_table() → 按需导出 CSV。
- 扫描版 PDF 不能直接用 pdfplumber 读文字,需 OCR。