PDF 提取文字为什么有时空白、有时乱码:文字层 / OCR / 表单字段三种来源

· 约 7 分钟 📜 PDF 提取文字

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. 按坐标排序(或不排序)输出

会出错的环节

  1. ToUnicode CMap 缺失或损坏 阅读器渲染时直接画字形(不需要 Unicode),但复制和提取必须经过 ToUnicode。一些 PDF 写不出或写错 ToUnicode:

    • 国产 PDF 工具的早期版本
    • 老旧 LaTeX 输出(特别是中文)
    • “防爬虫”刻意混淆 CID 映射的 PDF
    • 字体子集化时漏掉 ToUnicode 信息

    症状:字体显示正常,复制出来全是乱码 / □ / 私有区字符(U+E000–U+F8FF)。

  2. CID 字体没有 Unicode 等价 罕见的中文古字、生僻字、自造字 → 字体里有字形但 Unicode 没对应码点 → ToUnicode 标记为 “PUA”(私有用户区)→ 复制出来是 PUA 字符。

  3. 顺序问题(详见后文)

  4. 粘字 / 漏空格 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 -layoutlayout-aware按 y / x 坐标重排,多栏可用
pdftotext -rawraw完全不重排,喂下游算法
PyMuPDF get_text("text")默认简单按 block
PyMuPDF get_text("blocks")块级返回 (x0, y0, x1, y1, text) 元组
PyMuPDF get_text("dict")字典级字符级坐标和字体信息
pdfplumberlayout-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/) 或本地命令行

实务清单

必做

  1. 提取前先选中复制看是否乱码——决定走文本路径还是 OCR 路径
  2. 长 PDF 提取后翻头部几页核对顺序
  3. 多栏 / 表格 / 复杂排版用 layout-aware 提取器
  4. OCR 前确保扫描分辨率 ≥ 300 DPI
  5. 中文为主选 PaddleOCR,跨语言选 Tesseract,最准选 ABBYY
  6. 涉及表单先 dump_data_fields 看字段值,再扁平化处理

避免

  1. 拿到扫描件还试图 pdftotext(提不出字)
  2. ToUnicode 损坏的 PDF 强行做后处理(直接 OCR 更快)
  3. pdftotext 默认参数处理多栏论文(顺序必乱)
  4. 把表格当普通文本提(要走 pdfplumber / camelot)
  5. 把 OCR 结果不复核直接喂给 LLM(OCR 错字会让回答错得更自信)
  6. 在线工具上传敏感 PDF(合同 / 财务 / 身份信息)

PDF 提取文字的真相:“提不出来”和”提乱了”是两个不同的问题——前者要换路径(OCR / 表单),后者要换工具(layout-aware / 后处理)。看错了症状就一直走死胡同。

❓ 常见问题

我能在 PDF 阅读器里选中复制文字,但用工具批量提取出来全是乱码,为什么?

几乎肯定是 ToUnicode / CMap 缺失或损坏。PDF 文字层存的不是 Unicode,而是字体内部的 GID(glyph ID)或 CID(character ID)——一个数字代表"该字体里第 N 个字形"。ToUnicode CMap 是 PDF 内的一个表,把 CID 映射回 Unicode;阅读器复制时走这张表,所以选得中、复制对。有些 PDF 工具(特别是国产工具或老 LaTeX 输出)不写或写错 ToUnicode 表 —— 阅读器渲染没问题(直接画字形),但提取出来就是 CID 数字 / 私有区符号 / 乱码。判断方法:在阅读器里复制一段中文 → 粘贴到记事本 → 显示正常说明 ToUnicode OK,乱码说明缺失。临时补救:跑 OCR 重新生成文字层(拿渲染图反认)。

扫描件 PDF 怎么转成可提取的文字?OCR 选什么?

走 OCR 加文字层。扫描件内部就是图像,没有任何文字对象,普通文本提取拿到空字符串。主流 OCR 引擎:(1) Tesseract(开源,免费,中文模型 chi_sim / chi_tra 准确度中等);(2) PaddleOCR(百度开源,中文优势明显,速度快);(3) ABBYY FineReader(商业,准确度顶级,价格贵);(4) Adobe Acrobat 的 OCR(集成度好,中文一般)。输出形式:(1) 直接生成 TXT —— 失去版面;(2) 生成"sandwich PDF" —— 视觉上是原图,下面叠加不可见文字层,可选可搜,最常用;(3) 生成结构化 JSON / hOCR —— 带坐标和块层级。陷阱:扫描分辨率 < 200 DPI 的图,无论用哪种 OCR 都不准,前提是把扫描质量做好。

PDF 表单填的内容能被普通文本提取拿到吗?

多数情况下拿不到,需要专门读表单字段。PDF 表单分两种:AcroForm(标准 PDF 表单字段)—— 字段值独立存在 PDF 的 /AcroForm 对象里,不一定会被绘制到页面(取决于 PDF 是否做了"扁平化")。多数 pdftotext 等通用工具只读页面绘制流,提不出未扁平化的字段值。XFA(Adobe XML 表单架构)—— 字段值存在内嵌的 XFA datasets XML 里,几乎所有非 Adobe 工具都拿不到。怎么取:(1) 用 pdftk: pdftk input.pdf dump_data_fields 列出 AcroForm 字段;(2) 用 Python 的 pypdf / pikepdf 读 /AcroForm/Fields;(3) 先做"扁平化"(把字段值绘成普通文本对象)再用文本提取——但扁平化后字段就不能再修改。判断方式:阅读器里能直接选中复制 → 已扁平化或绘制到页面;选不中只能在表单字段里看 → 未扁平化。

为什么提取出来文字顺序乱了,明明 PDF 看起来很整齐?

PDF 文字流的存储顺序 ≠ 视觉阅读顺序。PDF 创建时按"画图指令"的顺序记录文本对象——绘制软件什么顺序输出由它决定。Word 导出的 PDF 通常按阅读顺序,但 LaTeX、InDesign、PowerPoint 经常乱序:可能先画第二段再画第一段、可能从右下角开始画、可能图里的标注插在正文中间。典型乱序场景:(1) 多栏排版——左栏第一段后突然跳到右栏;(2) 图文混排——图片标注插入正文;(3) 表格——按行存储但每个单元格内部按列;(4) 页眉页脚——可能在内容前后;(5) 公式——LaTeX 公式内字符按渲染顺序,提取出来全错。修复方式:用支持"layout-aware"的提取器(pdftotext 加 -layout、PyMuPDF 的 get_text("blocks")、PaddleOCR PDF 解析)能按视觉块重排,但效果仍受原 PDF 质量影响。

多栏 / 表格的 PDF 提取出来很烂,怎么办?

这是 PDF 文本提取最难的场景。PDF 没有"列"或"表格"的语义,只有一堆带坐标的字符——重建成结构化数据需要算法去推断。思路:(1) 用 layout-aware 提取器先按 y 坐标聚类成行、按 x 坐标聚类成列——pdftotext -layout / pdfplumber / tabula-py 等专门处理表格;(2) 多栏排版用"列检测算法"识别出栏分隔线,再分别提取每栏;(3) 复杂表格直接 OCR + 表格识别(Microsoft Read、PaddleOCR PP-Structure 都有专用模型)。实务:(1) 提取财报 / 招股书表格 → tabula / camelot;(2) 提取多栏论文 → PyMuPDF 的 blocks 模式;(3) 提取扫描表格 → PP-StructureV2;(4) 都不行 → 一页一页人工复核。

提取出来文字里夹着奇怪空格、断词、连字符,怎么处理?

几个典型陷阱与处理。(1) 行尾连字符——英文 PDF 行尾的"hy-phen"换行后变成"hy-\nphen",提取出来要拼回去:删除行尾 -\n;(2) CJK 字符之间多余空格——某些 PDF 在每个汉字之间插空格做"两端对齐",提取后删除"汉字+空格+汉字"中的空格;(3) 不可见字符——软连字符 U+00AD、零宽空格 U+200B、不间断空格 U+00A0 都肉眼看不见但实际存在;(4) NFC / NFD 归一化——同一个字符可能存成"基字符 + 组合记号"或单一组合字符,比对前先 unicode 归一化;(5) 同形异码——半角 vs 全角数字、全角 vs 半角空格,看场景决定要不要统一。实务:写正则做后处理或用专门的 PDF 文本清洗库(如 unstructured)。

提取后能保留粗体、字号、列表层级这些格式吗?

纯文本提取不保留,结构化提取部分保留纯文本工具(pdftotext / PDF.js textContent)只输出字符序列,丢失所有格式。结构化提取:(1) PyMuPDF / pdf-lib 能读到每个文本对象的 font name / font size / color / bbox,可以根据字号 + bold flag 推断"这是标题";(2) unstructured / markitdown 等库进一步把 PDF 转成 Markdown,保留标题层级、列表、表格;(3) 效果不稳——靠启发式规则,复杂排版经常出错。实务:(1) 简单文档 → markitdown 转 Markdown 够用;(2) 复杂文档 → 直接看原 PDF + 手工整理;(3) 喂给 LLM → 多数情况"纯文本 + 描述这是 PDF" 比 markdown 更稳,因为格式错了反而误导。

PDF 提取后想喂给 LLM 做摘要 / 问答,要注意什么?

别直接把 pdftotext 输出怼进去。(1) 顺序问题:上一题提到的乱序问题对 LLM 致命——LLM 顺着读,文本顺序乱它就懵;先用 layout-aware 提取或人工核对头部几页;(2) 表格问题:表格在纯文本里是空格 / 制表符堆,LLM 经常把行列搞错;改成 markdown 表格或直接喂图(多模态 LLM);(3) 公式问题:LaTeX 公式提取后是字符乱序,专门的 mathpix / nougat 能识别公式回 LaTeX;(4) 章节边界:长 PDF 切片时按章节切,别按字符数硬切,否则可能切在公式中间;(5) 隐私:上传 PDF 给 LLM 服务前确认是否敏感,本地解析后再选择性上传更稳。

📜 打开 PDF 提取文字 TXT / Markdown · 启发式标题段落 · 范围选择