PDF 不是 Word——它没有”段落树”,只有一堆”在某个 (x, y) 画了什么字形”的指令。所谓”提取文字”,实际是把这堆指令翻译回字符流。
而 PDF 内部”文字”的来源至少有三种,每种的提取路径完全不同——这也是为什么同样一份 PDF,有人秒提、有人提出乱码、有人提出空白。
三种文字来源
PDF 内部"文字"的三种来源
│
├─ ① 真文字层(最理想)
│ PDF 创建时直接写入文本对象
│ 来源:Word / LaTeX / Markdown / HTML 转换
│ 每个文本对象 = 字体 + 字符 + 坐标
│ ✓ 直接读,但可能乱码、错序、粘字、断词
│
├─ ② OCR 文字层(半理想)
│ 原 PDF 是图像,OCR 后叠加"不可见文字"
│ 来源:扫描件 + Tesseract / PaddleOCR / Acrobat OCR
│ ✓ 可选可搜,但准确度看 OCR 引擎和图质
│
└─ ③ 表单字段(特殊)
AcroForm: 字段值在 /AcroForm/Fields 对象里
XFA: 字段值在内嵌 XML datasets 里
✗ 普通文本提取拿不到,需要专门读字段
判断你手上的 PDF 是哪种:
| 现象 | 来源 |
|---|---|
| 阅读器能选中文字、复制粘贴正常 | 真文字层(或 OCR 后) |
| 能选中但复制出来是乱码 / 私有符号 | 真文字层(ToUnicode 损坏) |
| 选不中文字、字像图 | 扫描件(未 OCR) |
| 选中能选到但是矩形块 | OCR 文字层(不可见层) |
| 阅读器里有”输入框”,输入有效 | 表单字段(AcroForm / XFA) |
① 真文字层:最常见,但仍可能出错
正常路径:
PDF 创建(Word 导出 / LaTeX)
↓
内部有文本对象:
Tj 操作符画字符
Font 资源含字体定义
ToUnicode CMap 把 CID → Unicode
↓
提取工具(pdftotext / PDF.js / pdfminer):
1. 读 Content Stream 拿到字符序列(CID 流)
2. 通过 ToUnicode CMap 翻译成 Unicode
3. 按坐标排序(或不排序)输出
会出错的环节:
-
ToUnicode CMap 缺失或损坏 阅读器渲染时直接画字形(不需要 Unicode),但复制和提取必须经过 ToUnicode。一些 PDF 写不出或写错 ToUnicode:
- 国产 PDF 工具的早期版本
- 老旧 LaTeX 输出(特别是中文)
- “防爬虫”刻意混淆 CID 映射的 PDF
- 字体子集化时漏掉 ToUnicode 信息
症状:字体显示正常,复制出来全是乱码 / □ / 私有区字符(U+E000–U+F8FF)。
-
CID 字体没有 Unicode 等价 罕见的中文古字、生僻字、自造字 → 字体里有字形但 Unicode 没对应码点 → ToUnicode 标记为 “PUA”(私有用户区)→ 复制出来是 PUA 字符。
-
顺序问题(详见后文)
-
粘字 / 漏空格 PDF 没有”空格分词”概念,“hello world” 可能是两个相邻文本对象,工具靠坐标差判断要不要插空格——阈值不对就要么粘成 helloworld,要么塞太多空格。
② OCR 文字层:扫描件的唯一路径
扫描件 PDF 内部是图像,没有任何文本对象,普通文本提取拿到空字符串。必须先做 OCR。
OCR 引擎对比:
| 引擎 | 中文准确度 | 速度 | 特点 |
|---|---|---|---|
| Tesseract | 中(chi_sim 模型) | 中 | 开源,离线,命令行友好 |
| PaddleOCR | 高(中文 SOTA 之一) | 快 | 百度开源,专攻中文,PP-Structure 还能识别表格 |
| ABBYY FineReader | 顶级 | 中 | 商业,价格贵,准确度业界标杆 |
| Adobe Acrobat OCR | 中 | 中 | 集成在 Acrobat,方便 |
| Microsoft Azure Read | 高 | 快(云) | API 调用,云服务 |
OCR 输出形式:
| 形式 | 用途 |
|---|---|
| 纯文本 TXT | 失去版面,只看内容 |
| Sandwich PDF | 视觉上是原图,下面叠加不可见文字层,可选可搜(最常用) |
| hOCR / ALTO XML | 带坐标和块层级,喂给 RAG / 知识库 |
| Markdown / HTML | 保留章节、表格层级(PaddleOCR PP-Structure / unstructured 输出) |
OCR 失败的常见原因:
扫描分辨率 < 200 DPI → 字符发糊,模型看不清
扫描歪斜未校正 → 文字行斜的,行检测出错
扫描带阴影 / 透明印章 → 字符被遮挡
低对比度(淡墨 / 老旧文档)→ 阈值化失败
混合横竖排 → 引擎默认横排,竖排被识别成单字
实务:扫描质量永远比 OCR 算法重要——300 DPI 黑白清晰扫描 + Tesseract,效果远好于 100 DPI 模糊扫描 + 顶级商业 OCR。
③ 表单字段:被忽略的”隐藏内容”
PDF 表单是用户在阅读器里填的”输入框 / 复选框 / 下拉”——值不一定绘制到页面上。
两种表单:
AcroForm(标准 PDF 表单)
字段值存储位置:PDF 的 /AcroForm/Fields 对象
渲染:可选——可以让填写值显示在页面也可以不显示
扁平化(flatten):把字段值绘成普通文本对象,之后字段就锁死
XFA(Adobe XML Forms Architecture)
字段值存储位置:PDF 内嵌的 XFA datasets XML
渲染:用 XFA 引擎动态生成(只有 Adobe 系工具完整支持)
非 Adobe 工具几乎都拿不到 XFA 字段值
怎么取 AcroForm 字段:
# 用 pdftk 列出所有字段名和值
pdftk input.pdf dump_data_fields > fields.txt
# 用 qpdf 看 AcroForm 对象
qpdf --json input.pdf | jq '.objects | with_entries(select(.value.T))'
# Python: pypdf
from pypdf import PdfReader
reader = PdfReader("form.pdf")
fields = reader.get_form_text_fields()
for k, v in fields.items():
print(k, "=", v)
扁平化让普通工具也能提:
# pdftk: 把字段值固化成页面文字
pdftk input.pdf flatten output flat.pdf
# 之后用 pdftotext 等工具就能提到
实务:拿到带表单的 PDF 先看用户填了没——先 dump_data_fields 看字段值;如果值就在那,扁平化后再做下游处理。
文本顺序问题:PDF 文本提取的头号坑
PDF 文本对象按”绘制顺序”存储,绘制顺序未必等于阅读顺序。
典型乱序场景:
1. 多栏排版(论文 / 报纸)
左栏第一段 → 突然跳右栏第一段 → 回左栏第二段
原因:排版引擎可能按列、按字号、按图层处理
2. 图文混排
图片标注被插在正文中间
"正文 1 → 图标题 → 图说明 → 正文 2"
3. 表格
按行存储但单元格内按列
"A1 B1 A2 B2 C1 C2" 而不是 "A1 B1 C1 A2 B2 C2"
4. 页眉页脚
有的引擎先画页眉再画正文,有的反之
5. 公式 / 数学排版
LaTeX 公式内字符按 TeX 渲染顺序
"x²+y²=z²" 提取出来可能是 "x2y2z2+="
6. 注释 / 边注 / 浮动框
主流之外的元素位置不可控
修复:
| 工具 | 模式 | 效果 |
|---|---|---|
pdftotext | 默认 | 简单按对象顺序,乱序高发 |
pdftotext -layout | layout-aware | 按 y / x 坐标重排,多栏可用 |
pdftotext -raw | raw | 完全不重排,喂下游算法 |
PyMuPDF get_text("text") | 默认 | 简单按 block |
PyMuPDF get_text("blocks") | 块级 | 返回 (x0, y0, x1, y1, text) 元组 |
PyMuPDF get_text("dict") | 字典级 | 字符级坐标和字体信息 |
pdfplumber | layout-aware | 表格抽取最强 |
unstructured | 智能 | 调用多种策略,输出 Markdown |
经验:长 PDF 别直接 pdftotext 一刀切——先翻 1-2 页看顺序对不对,确认了再批量。
中文 PDF 的特殊问题
1. CID-keyed font
中文 PDF 几乎都用 CID 字体(一个字符 = 多字节 CID)
提取依赖 ToUnicode CMap,缺了就乱码
2. 字符间隙做"两端对齐"
"中 国 人民" ← 实际是"中 国 人 民",每字之间一个 0.x em 的间隙
pdftotext 可能误判为空格,输出"中 国 人 民"
后处理:把"汉字+空格+汉字"的空格删掉
3. 古汉字 / 异体字 / 生僻字
字体有字形但 Unicode 找不到对应码点
ToUnicode 标 PUA → 复制出来是 □ / 私有区字符
4. 横竖混排
古籍 / 日文 PDF 常竖排
多数 OCR 默认横排,竖排识别成"单列"
工具选型决策
我要从 PDF 提取文字
│
├─ PDF 是文字 PDF(鼠标能选中)
│ ├─ 复制能正常 → pdftotext / PyMuPDF
│ └─ 复制是乱码 → ToUnicode 坏了,跑 OCR 重建文字层
│
├─ PDF 是扫描件(鼠标选不中)
│ └─ 跑 OCR
│ ├─ 一次性少量 → Adobe Acrobat OCR
│ ├─ 中文为主、批量 → PaddleOCR
│ ├─ 跨语言、批量 → Tesseract
│ └─ 准确度第一 → ABBYY FineReader
│
├─ PDF 是表单
│ ├─ AcroForm → pdftk dump_data_fields / pypdf
│ └─ XFA → 只能 Adobe Acrobat
│
├─ 内容是表格 → pdfplumber / camelot / tabula
│
├─ 喂给 LLM → unstructured / markitdown 转 Markdown
│
└─ 涉及隐私 → 浏览器内 wasm 工具:[PDF 提取文字](/tools/pdf-to-text/) 或本地命令行
实务清单
✅ 必做:
- 提取前先选中复制看是否乱码——决定走文本路径还是 OCR 路径
- 长 PDF 提取后翻头部几页核对顺序
- 多栏 / 表格 / 复杂排版用 layout-aware 提取器
- OCR 前确保扫描分辨率 ≥ 300 DPI
- 中文为主选 PaddleOCR,跨语言选 Tesseract,最准选 ABBYY
- 涉及表单先 dump_data_fields 看字段值,再扁平化处理
❌ 避免:
- 拿到扫描件还试图 pdftotext(提不出字)
- ToUnicode 损坏的 PDF 强行做后处理(直接 OCR 更快)
- 用
pdftotext默认参数处理多栏论文(顺序必乱) - 把表格当普通文本提(要走 pdfplumber / camelot)
- 把 OCR 结果不复核直接喂给 LLM(OCR 错字会让回答错得更自信)
- 在线工具上传敏感 PDF(合同 / 财务 / 身份信息)
PDF 提取文字的真相:“提不出来”和”提乱了”是两个不同的问题——前者要换路径(OCR / 表单),后者要换工具(layout-aware / 后处理)。看错了症状就一直走死胡同。