pdf-inspector
人生最宝贵的是生命,人生最需要的是学习,人生最愉快的是工作,人生最重要的是友谊。——斯大林
当 PDF 不再一股脑丢给 OCR:认识一下 pdf-inspector
很多 PDF 处理流程里,最贵的那一步,往往不是“读文件”,而是“看不清就全送去 OCR”。
这件事听上去很自然,做起来也很省心:PDF 来了,扔进 OCR,等结果出来。但问题也很明显——有些 PDF 根本不是扫描件,它本来就带着清清楚楚的文本层;如果还是按同样的重型流程处理,时间和成本都会被悄悄放大。
pdf-inspector 想解决的,正是这个环节里的“误伤”。
项目地址:/firecrawl/pdf-inspector
它是一个用 Rust 编写的 PDF 检查与提取库,核心目标很直接:先判断 PDF 到底是什么类型,再决定后续该怎么走。README 对它的描述非常清楚——它可以做 PDF 分类、文本提取,并且能智能识别一个 PDF 是扫描件还是文本型文档,从而帮助上层流程做更聪明的路由决策。
它不是在“读取 PDF”,它更像是在“打量 PDF”
如果把普通 PDF 处理工具比作一位埋头苦干的搬运工,那 pdf-inspector 更像是站在门口先看一眼局势的人。
它先问的不是“怎么提取”,而是:
- 这是文本型 PDF 吗
- 这是扫描件吗
- 这是纯图像型文档吗
- 还是一份混合型 PDF
README 里给出的分类结果包括:
TextBasedScannedImageBasedMixed
而且这个判断不是一句模糊的“像不像”,它还会返回 confidence score,也就是 0.0 到 1.0 的置信度分数;同时还能给出 按页的 OCR 路由信息,指出哪些页面缺少文本、需要 OCR 介入。
这就很有意思了。因为现实里的 PDF 从来不是整齐划一的:有的文档前几页是正文,后几页是扫描附录;有的报告主体是文字,但夹着几页图片;有的材料明明能直接提文本,却总被整个送进 OCR。pdf-inspector 在这里做的事情,不是粗暴地把整份文档贴上一个标签,而是尽可能把判断拆细,把决策交给真正需要决策的地方。
一个很务实的出发点:不该做 OCR 的,就别做 OCR
README 里对这个项目的定位非常鲜明:它由 Firecrawl 构建,用来在本地处理文本型 PDF,并跳过昂贵的 OCR 服务。
甚至连使用场景都写得很直白——针对大规模 PDF 处理流程,不是把每份 PDF 都送去 OCR,而是先分类:
1 | PDF arrives |
这个流程图的价值在于,它没有把工具包装成“万能 PDF 魔法盒”,而是非常诚实地告诉你:它特别适合做前置判断与本地提取。当文档本身已经具备良好的文本结构时,就本地快速完成;当文档缺少可用文本层,再转去 OCR。
README 还提到,之所以这么做,是为了节省大多数本来就是文本型 PDF 所带来的成本与延迟。这一点很朴素,但也很关键。很多工程优化最后都不是更复杂,而是更会分流。
这份 README 的关键词只有两个:快,且知道自己在做什么
pdf-inspector 的“快”不是一句空泛宣传,README 里把它拆成了多个具体层面。
1. 分类很快
项目说明中写到,它通过采样内容流来检测 PDF 类型,分类大约在 10 到 50 毫秒之间完成。对于 300 页以上的 PDF,也能在毫秒级完成检测。
README 还解释了分类思路:
- 解析 xref table 和 page tree,而不是完整加载所有对象
- 根据
ScanStrategy选择页面,默认扫描全部页面并支持提前退出 - 在内容流里查找
Tj/TJ这样的文本操作符,以及Do这样的图像操作符 - 根据采样页中的文本操作符情况进行分类
这套思路很像一个经验老到的检查员:不急着把整栋楼拆开重装,而是先看结构、查关键线索,再决定判断。
2. 提取也快
README 里提到,这个库可以在本地 200ms 以内处理文本型 PDF。与此同时,它还有一个重要的架构设计:文档只加载一次。
也就是说,检测和提取共享同一个已解析文档,不会为了“先判断、再提取”而重复做 I/O 和重复解析。这个细节非常工程化,也很能说明作者在意的不是孤立功能点,而是整条处理链的效率。
它提取的不只是“字”,而是尽量保留“文档的样子”
如果一个 PDF 工具只能把文字抠出来,那它离“可用”还差一步;真正难的是,把文本顺序、结构层级、列表、表格这些信息尽量保留下来。
pdf-inspector 在 README 里把这一块写得相当完整,它支持的并不是单纯文本输出,而是 带位置感知的提取,并进一步转换为 干净的 Markdown。
文本提取:位置意识很强
项目支持带有以下信息的文本提取:
- 字体信息
- X/Y 坐标
- 自动多栏阅读顺序
这意味着它不是在“把字符拼一遍”,而是在尽可能理解页面布局。尤其面对报纸式、多栏式排版时,这一点很重要。很多文档如果只按原始顺序硬拼,最后读起来会像把两条河流拧在了一起;而 pdf-inspector 明确支持 自动检测多栏布局,并按顺序组织阅读结果。
Markdown 转换:不是简化,而是整理
README 里列出了 Markdown 输出阶段会处理的内容,包括:
- 标题 H1 到 H4,通过字体大小层级识别
- 粗体和斜体,通过字体名称模式识别
- 项目符号列表
- 编号列表
- 字母列表
- 代码块,通过等宽字体和关键字检测
- 表格
- 财务表格中的数字拆分
- 图表说明文字
- 上标和下标
- URL 转换为 Markdown 链接
- 断行连字符恢复
- 页码过滤
- 首字下沉合并
- 目录式点线压缩
看起来像是一长串细节,但这些细节组合起来,其实是在回答同一个问题:导出的 Markdown 到底能不能直接读、直接用。
一份好的结构化提取,不是把页面“打散”,而是把它“翻译”成另一种仍然有秩序的表达方式。pdf-inspector 做的就是这件事。
表格这件麻烦事,它没有回避
PDF 里的表格,向来是文档处理里的硬骨头。pdf-inspector 在 README 中把表格检测单独拿出来写,也说明这部分是它的重要能力之一。
它采用的是 双模式表格检测:
- 基于 PDF 绘图操作中的矩形进行检测
- 基于文本对齐关系进行启发式检测
随后再做:
- 列与行分配
- 单元格组织
- 最终格式化为 Markdown 表格
README 还特别提到,它可以处理:
- financial tables
- footnotes
- continuation tables across pages
也就是说,它考虑的不是“有没有一张最标准的表”,而是现实里那些经常不那么整齐、甚至会跨页延续的表格。
这是一种很实在的设计态度:PDF 不是给解析器准备的,它原本是给人看的,所以所有结构化提取都天然带着一点“逆向理解”的意味。表格做得越认真,越说明项目没有停留在“能跑通”这个层面。
对字体编码问题,它保持警觉
不少 PDF 处理问题,看起来像“提取失败”,实际上根源常常藏在字体编码里。
README 里提到,pdf-inspector 支持:
- ToUnicode CMap 解码
- Type0 / Identity-H 字体
- UTF-16BE
- UTF-8
- Latin-1 编码
同时,它还会自动检测 broken font encodings,并在发现问题时提示调用方回退到 OCR。
这一点很重要,因为“错误地提取出一堆乱码”往往比“明确告诉你这里不可靠”更糟糕。一个靠谱的系统,不只是会成功,也知道什么时候该承认自己不该继续硬提。
同一套核心,不只活在 Rust 里
虽然它的核心是 Rust,但这个项目并没有把自己困在单一语言生态里。README 展示了它在多个环境里的使用方式:
- Python
- Node.js
- Browser WebAssembly
- Rust
- CLI
这不是“顺手做了几个绑定”,而是让同一个 PDF 能力,在不同上下文里都能被调用。
Python:直接处理文件
1 | import pdf_inspector |
Node.js:直接读取 Buffer 处理
1 | import { readFileSync } from 'fs'; |
浏览器 WebAssembly:本地跑,不走服务端
1 | import init, { processPdf } from '@firecrawl/pdf-inspector-wasm'; |
Rust:最原生的一种方式
1 | use pdf_inspector::process_pdf; |
CLI:给命令行工作流留足空间
1 | # Install the CLI tools |
从这些入口能看出来,pdf-inspector 的姿态很明确:它不只想成为一个库,也想成为一块可嵌入、可集成、可组合的基础能力。
浏览器侧的 WASM 版本,有一种“把事情留在本地”的克制
WASM 版本在 README 里有几个很值得注意的点:
- 解析在本地运行
- PDF bytes 不会上传
- 构建是单线程的
- 不要求 cross-origin isolation
- CMaps 已内嵌
init()之后提取是同步的- 大文档建议放到 Web Worker 里执行
- 纯图片文档依然需要单独 OCR
这几条信息拼起来,勾勒出了一个很清晰的浏览器侧能力边界:它能在浏览器里本地做分类和结构化提取,但不会假装自己能替代图像 OCR。
这种边界感很可贵。很多工具最容易让人失望的地方,是宣传成“全能型”,实际遇到复杂文档就沉默;而这里的说明方式更像一个稳重的工程组件——能做什么,说清楚;不能替代什么,也说清楚。
Node.js 绑定里,还有一项很实用的能力:区域提取
napi/README.md 里提到一个主 README 没有重点展开、但非常实用的能力:按区域提取文本。
也就是:
1 | extractTextInRegions(buffer: Buffer, pageRegions: PageRegions[]): PageRegionTexts[] |
它的设计目标也写得很明白:适用于 混合 OCR 流程。先由页面图像上的布局模型识别区域,再由这个函数从 PDF 结构层里提取对应区域的文本。
示例里每个区域结果都带有 needsOcr 标记;如果文本不可靠,比如:
- 提取结果为空
- 字体是 GID 编码
- 出现垃圾文本
- 编码异常
那就把这个区域继续送 OCR。
这种能力很像是把“整份 PDF 是否 OCR”进一步细化成“某一页、某一块区域是否 OCR”。对于复杂文档来说,这种粒度显然更灵活。
它的内部结构,读起来很像一条分工明确的流水线
README 给了一张很完整的架构图,从原始 PDF bytes 开始,分成 detector 和 extractor 两条主线。
提取链路里又包括:
- fonts
- content_stream
- xobjects
- links
- layout
然后布局再进入:
- tables
- markdown
最后经过分析、预处理、转换、分类和后处理,产出最终 Markdown。
这种结构感带来的最大好处,是让人一眼看出项目不是“功能堆在一起”,而是按职责拆开的。README 还列出了源代码目录结构,例如:
lib.rs负责公共 API、PdfOptionsbuilder 和便捷函数python.rs负责 PyO3 Python 绑定types.rs定义共享类型text_utils.rs处理字符与文本辅助逻辑process_mode.rs定义处理模式detector.rs负责快速 PDF 类型检测glyph_names.rs提供 Adobe Glyph List 到 Unicode 的映射tounicode.rs处理 ToUnicode CMap 解析extractor/是文本提取流水线tables/负责表格检测与格式化markdown/负责 Markdown 转换与结构识别bin/放置 CLI 工具napi/是 Node.js/Bun 绑定wasm/是浏览器绑定
如果你喜欢看项目“骨架”,这一部分会很有阅读价值。它没有试图用很多抽象术语把自己说得高深,反而是把模块划分摆在你面前,让你知道这套能力是怎么层层拼起来的。
扫描策略也不是只有一种
在分类环节里,README 还给出了 ScanStrategy 的几种策略:
EarlyExit:扫描所有页面,遇到第一个非文本页就停止Full:扫描所有页面,不提前退出Sample(n):均匀采样若干页Pages(vec):只扫描指定页面
这意味着项目并不是只有一种固定的“检测姿势”,而是把速度和精度之间的权衡显式交给调用方。
如果你的场景更看重快速路由,可以用更偏速度的策略;如果你需要更准确地区分 Mixed 和 Scanned,也有对应选择。这样的设计很适合真实系统接入,因为不同业务场景对“快”和“准”的容忍度并不相同。
从 Benchmark 看,它想证明的不只是能用,而是有竞争力
README 提供了一组基于 opendataloader-bench 语料库的 benchmark 结果,测试集包含 200 份 PDF,只展示本地引擎且关闭 OCR。
结果表里,pdf-inspector 的成绩是:
- Overall:0.875
- Reading Order (NID):0.915
- Tables (TEDS):0.814
- Headings (MHS):0.788
- Speed (200 docs):0.470s
README 还写到,这组结果在 2026 年 7 月 31 日于 Apple M4 Pro 上刷新;速度统计是去掉预热后的五次完整跑分中位数。
项目还明确写出一条“Best fit”总结:它适合 native-text PDFs,尤其是在速度、阅读顺序和表格结构都重要的场景里。在这次对比中,它拿到了更高的整体分、阅读顺序分和表格分,同时也是最快的。
这段 benchmark 信息的价值,不只是“我更强”,而是它把“适合什么场景”说清楚了:文本型 PDF、重视结构质量、同时在意速度。这和它前面的整体定位是连贯的。
安装方式很直接,门槛并不高
如果你想从 README 给出的路径快速上手,不同环境的入口都非常明确。
Python
1 | pip install maturin |
Node.js
1 | npm install @firecrawl/pdf-inspector |
Browser WebAssembly
1 | npm install @firecrawl/pdf-inspector-wasm |
Rust
1 | cargo add pdf-inspector |
或者手动添加依赖:
1 | [dependencies] |
从源码构建 WASM
1 | cargo install wasm-pack --version 0.15.0 --locked |
Node.js 绑定的 README 里还特别提到,它提供预构建二进制,覆盖 Linux x64/ARM64、macOS ARM64 和 Windows x64,npm 安装时只会安装与你平台匹配的那个包,而且 不需要 Rust toolchain。这对使用者来说非常友好,尤其是在只想集成能力、不想处理本地编译链路的时候。
一个很有“工程气”的 PDF 工具
读完 README 和 description,会很明显地感受到 pdf-inspector 的气质:它不是那种试图在文案里包办一切的项目,而是一块边界清楚、能力扎实、特别适合接进处理流水线的基础组件。
它关心的不是“把 PDF 这件事说得多宏大”,而是几个很具体的问题:
- 这份 PDF 是文本型还是扫描型
- 哪些页需要 OCR
- 能不能本地快速提取文本
- 能不能尽量保住阅读顺序和结构
- 能不能把结果整理成更干净的 Markdown
- 遇到编码问题时,能不能及时识别并让上层回退
这种专注会让一个工具显得很可靠。因为它不是对所有问题都说“我能”,而是把自己最擅长的那一段路打磨得很深。
如果说 OCR 像重型机械,适合啃最硬的文档;那么 pdf-inspector 更像是一位懂分流、会判断、下手快的前场指挥。它先看看眼前这份 PDF 究竟是什么,再决定是轻快地本地处理,还是把真正难啃的部分交给后面的流程。
在 PDF 处理这条链路里,这样的角色,往往比想象中更重要。
