2026/07/30

PDF 处理链别一上来就 OCR:pdf-inspector 如何先用本地解析完成分类、路由与 Markdown 抽取

pdf-inspector 用 Rust 核心完成 PDF 分类、置信度判断、逐页 OCR 路由和 Markdown 抽取,让 TextBased 与 Mixed 文档先走本地解析,扫描页再交给后续 OCR。

pdf-inspectorPDFOCRMarkdownRust

PDF 处理链里最昂贵、也最容易被误用的一步,往往是 OCR。很多系统拿到文件后直接把每一页渲染成图片,再交给 OCR 或视觉模型识别,流程看起来统一,实际却把大量本来带有文字层的 PDF 重新变成图片处理。结果是延迟上升、成本上升、表格和阅读顺序仍然可能丢失,隐私敏感文档还要绕一圈外部服务。更稳的起点不是“先 OCR 再说”,而是先判断这个 PDF 到底属于哪一类:文字层是否可信,哪些页需要 OCR,哪些页可以在本地直接抽取结构化文本。

Firecrawl 的 pdf-inspector 把这个判断做成了一个很明确的 Rust 核心库:PDF classification 与 text extraction。它不做 OCR,不带机器学习模型,也不调用外部服务;它做的是在本地读取 PDF 结构、检测类型、抽取带位置信息的文本,并尽量转成干净的 Markdown。这个定位看似保守,实际非常适合放在文档流水线的第一层:能本地解析的先本地解析,不能可靠解析的再把具体页路由到后续 OCR。

先分类,再决定是否 OCR

pdf-inspector 的核心分类结果包括 TextBased、Scanned、ImageBased 和 Mixed。TextBased 表示 PDF 里有可用文字层,适合直接抽取;Scanned 通常意味着页面主要来自扫描图像,需要后续 OCR;ImageBased 更接近图片型文档,本地文本抽取价值有限;Mixed 则是企业文档里很常见的混合形态:前几页可能是可选中文本,附件、盖章页、扫描页或图片页需要另走 OCR。

这个分类不是给界面展示的标签,而是给路由系统用的控制信号。结果里有 confidence,调用方可以设置自己的策略:高置信 TextBased 直接走本地 Markdown;Mixed 文档先抽取可解析页,同时把 pages_needing_ocr 指出的页交给 OCR;低置信或存在编码问题的页面进入人工验证或更保守的 OCR 分支。这样一来,OCR 从默认入口变成兜底能力,既保留准确性,又避免对整本文档做不必要的图片化处理。

官方文档强调检测速度来自对内容流的采样,通常在几十毫秒量级;更关键的是,单次加载文档后检测和抽取共享同一个解析结果,避免“先检测读一遍、再抽取又读一遍”的重复 I/O。对批量处理发票、研报、合同、论文、尽调材料的系统来说,这种小设计会直接影响吞吐和排队延迟。

本地解析的价值不只是省钱

把本地解析放在 OCR 前面,最明显的收益是减少 OCR 调用次数。但更重要的是保留 PDF 原有结构。很多数字生成的 PDF,本来就包含字体、字号、坐标、段落和绘图操作。如果先渲染成图片,后续模型只能从像素重新猜文字位置、阅读顺序和表格边界;而 pdf-inspector 会从 PDF 对象和文本项里读取信息,保留 X/Y 坐标、字体、字号等位置感知数据,再基于这些信息组织阅读顺序。

这对 RAG、知识库和文档审查尤其重要。检索系统不只需要“识别出了哪些字”,还需要知道标题层级、列表、表格、跨栏顺序和页间断点。一个 OCR 字符串如果把左栏、右栏、脚注、页眉混在一起,后面向量检索再强也会受到污染。pdf-inspector 的路线是尽量在本地把 TextBased 页面整理成可读 Markdown,让后续 chunk、索引、引用定位和人工复核都有更干净的输入。

Markdown、layout、table 与 columns 是同一条链

pdf-inspector 的 Markdown 输出不是简单把文本按 PDF 内部顺序拼接。官方 README 描述了 headings、bullet/numbered/letter lists、code blocks、bold/italic、URL linking、page breaks 等转换能力;表格部分采用两类信号:一种来自 PDF 绘图操作里的矩形线框,另一种来自文本对齐的启发式判断。也就是说,它会把“看起来像表格”的文本排列和“PDF 里画出来的表格线”都纳入识别,而不是只依赖单一规则。

多栏文档也是 PDF 抽取的常见坑。论文、金融报告、报纸式资料经常把页面分成两栏或多栏,直接按对象出现顺序拼接会让段落交错。pdf-inspector 提供 multi-column reading order,并保留 text item 的位置和字体信息,适合在本地先形成更接近人类阅读顺序的文本。它还处理 RTL 文本和 CID/Type0 字体的 ToUnicode CMap 解码,对包含 CJK 字体或复杂编码的文档更友好。遇到疑似破损编码时,结果会暴露 has_encoding_issues 这类信号,调用方可以把它作为 OCR 或人工复核的触发条件。

这些能力共同指向一个实际架构:分类模块决定文档和页面级路由,抽取模块负责本地文本和位置证据,Markdown 模块生成给 RAG、搜索、审阅系统使用的正文,表格与多栏检测提供结构线索。它不是万能文档理解模型,而是一层快速、可嵌入、可解释的 PDF 预处理底座。

Python、Node、WASM、Rust 和 CLI 覆盖了不同入口

pdf-inspector 的实现核心是 Rust,许可证为 MIT。对 Python 项目,docs/python.md 展示了 process_pdf、detect_pdf、classify_pdf、extract_text_with_positions、extract_pages_markdown 等接口;文档说明 Python 包通过 PyO3 绑定 Rust 库,并提供 CPython 3.8 及以上的预编译 wheels,其他平台可从源码构建。Python 侧很适合作为数据管道、离线批处理、评测脚本或 RAG ingest 的第一步。

Node.js 和 Bun 项目可以看 napi/README.md。NAPI 版本面向 Web 服务和混合 OCR 管线,暴露 classifyPdf 和 extractTextInRegions 等接口。后者尤其适合“版面模型先在图片上找 region,本地 PDF 结构再尝试从同一区域抽文字”的流程;如果区域结果 needsOcr 为真,系统再把那一块送给 OCR,而不是整页整本文档都走 OCR。

浏览器侧的 wasm/README.md 说明了 @firecrawl/pdf-inspector-wasm 的使用方式。WASM 包在浏览器本地处理 Uint8Array,PDF 字节不需要上传到服务器;CMaps 被嵌入构建里,CJK 字体解码不依赖文件系统。它是单线程同步抽取,官方建议大文档放到 Web Worker 里避免阻塞 UI。对于隐私敏感的合同预检、浏览器端附件分类、前端上传前质量判断,这个入口很有意义。

Rust 用户可以直接阅读 docs/rust-api.md。Rust API 提供 process_pdf、detect_pdf、process_pdf_with_options、process_pdf_mem、extract_pages_markdown 等函数,还能通过 PdfOptions 调整处理模式、页码选择和检测策略。官方 Rust crate 当前版本为 0.1.6;NAPI 包版本为 1.11.1。CLI 入口则通过 cargo install pdf-inspector 获得,包含 pdf2md 与 detect-pdf:前者用于 PDF 转 Markdown,也支持 JSON 输出和页选择;后者用于快速分类和分析。CLI 在数据迁移、批量预筛和 benchmark 复现实验里很方便,因为它能直接嵌入 shell 管线。

benchmark 只能说明测试边界内的表现

官方 README 的 benchmark 使用 opendataloader-bench 的 200 个 PDF,OCR 被禁用,结果在 2026-07-16 于 Apple M4 Pro 上刷新。表格中 pdf-inspector 的 overall 为 0.875,reading order 为 0.915,tables 为 0.814,完整语料 median speed 为 2.8s。这个结果适合说明:在该语料、该评测器、该版本组合、OCR 关闭的前提下,pdf-inspector 在本地结构化解析上很有竞争力。

它不应被写成“所有 PDF 都能 2.8 秒处理完”或“表格永远优于 OCR/模型解析”。不同公司内部 PDF 的模板、扫描质量、字体编码、表格线条、印章遮挡和多语言混排差异很大,benchmark 只能作为选型起点。官方还提供 docs/benchmarking.md,用于在同一 OpenDataLoader 语料和 evaluator 版本上比较两个本地构建,避免拿不同语料修订和不同评测版本的结果横向拼接。真正上线前,仍然需要用自己的样本文档做回归集,特别是复杂表格、双栏论文、盖章合同、财报附注和扫描混合件。

限制反而让它的位置更清楚

pdf-inspector 最大的边界很明确:它不做 OCR。扫描 PDF、图片型 PDF、文字层缺失或严重乱码的页面,需要后续 OCR、视觉模型或人工复核。它也不声称能理解所有复杂版式;表格检测依赖矩形和对齐启发式,多栏阅读顺序也需要在真实业务文档中验证。对于极端版面、嵌套表格、旋转文本、水印遮挡、扫描件叠加隐藏文字层、错误 ToUnicode 映射等情况,调用方应根据 confidence、pages_needing_ocr、has_encoding_issues、pages_with_tables、pages_with_columns 等字段建立保守策略,而不是把本地解析结果无条件入库。

这正是它适合进生产链路的原因:边界足够窄,信号足够实用。一个合理的 PDF 处理架构可以这样设计:上传后先用 pdf-inspector detect 或 classify;TextBased 且高置信的页面直接抽 Markdown;Mixed 文档按页拆分,可抽取页保留本地 Markdown,不可靠页进入 OCR;ImageBased/Scanned 直接进入 OCR 队列;抽取结果和 OCR 结果最终在统一的页面结构里合并,并保留每页来源、置信度和复核标记。这样既不会浪费 OCR,也不会因为迷信本地解析而放过真正需要识别的扫描页。

把 OCR 放到后面,不是贬低 OCR,而是让它做该做的事。OCR 擅长从图像恢复文字,视觉模型擅长处理没有结构层的复杂页面;但对于原生数字 PDF,先用本地结构解析拿到文字、坐标、表格和 Markdown,往往更快、更便宜、更可控。pdf-inspector 的价值就在这个顺序调整里:先判断,再抽取,再把少数需要的页路由到 OCR。文档系统少一次盲目渲染,后面的搜索、RAG、审查和归档就多一层可靠输入。