
很多 PDF 处理流程里,最贵的那一步,往往不是 “读文件”,而是 “看不清就全送去 OCR”。
这件事听上去很自然,做起来也很省心:PDF 来了,扔进 OCR,等结果出来。但问题也很明显 —— 有些 PDF 根本不是扫描件,它本来就带着清清楚楚的文本层;如果还是按同样的重型流程处理,时间和成本都会被悄悄放大。
pdf-inspector 想解决的,正是这个环节里的 “误伤”。
项目地址:/firecrawl/pdf-inspector
它是一个用 Rust 编写的 PDF 检查与提取库,核心目标很直接:先判断 PDF 到底是什么类型,再决定后续该怎么走。README 对它的描述非常清楚 —— 它可以做 PDF 分类、文本提取,并且能智能识别一个 PDF 是扫描件还是文本型文档,从而帮助上层流程做更聪明的路由决策。
如果把普通 PDF 处理工具比作一位埋头苦干的搬运工,那 pdf-inspector 更像是站在门口先看一眼局势的人。
它先问的不是 “怎么提取”,而是:
README 里给出的分类结果包括:
TextBasedScannedImageBasedMixed而且这个判断不是一句模糊的 “像不像”,它还会返回 confidence score,也就是 0.0 到 1.0 的置信度分数;同时还能给出 按页的 OCR 路由信息,指出哪些页面缺少文本、需要 OCR 介入。
这就很有意思了。因为现实里的 PDF 从来不是整齐划一的:有的文档前几页是正文,后几页是扫描附录;有的报告主体是文字,但夹着几页图片;有的材料明明能直接提文本,却总被整个送进 OCR。pdf-inspector 在这里做的事情,不是粗暴地把整份文档贴上一个标签,而是尽可能把判断拆细,把决策交给真正需要决策的地方。
README 里对这个项目的定位非常鲜明:它由 Firecrawl 构建,用来在本地处理文本型 PDF,并跳过昂贵的 OCR 服务。
甚至连使用场景都写得很直白 —— 针对大规模 PDF 处理流程,不是把每份 PDF 都送去 OCR,而是先分类:
PDF arrives
→ pdf-inspector classifies it (~20ms)
→ TextBased + high confidence?
YES → extract locally (~150ms), done
NO → send to OCR service (2-10s)这个流程图的价值在于,它没有把工具包装成 “万能 PDF 魔法盒”,而是非常诚实地告诉你:它特别适合做前置判断与本地提取。当文档本身已经具备良好的文本结构时,就本地快速完成;当文档缺少可用文本层,再转去 OCR。
README 还提到,之所以这么做,是为了节省大多数本来就是文本型 PDF 所带来的成本与延迟。这一点很朴素,但也很关键。很多工程优化最后都不是更复杂,而是更会分流。
pdf-inspector 的 “快” 不是一句空泛宣传,README 里把它拆成了多个具体层面。
项目说明中写到,它通过采样内容流来检测 PDF 类型,分类大约在 10 到 50 毫秒之间完成。对于 300 页以上的 PDF,也能在毫秒级完成检测。
README 还解释了分类思路:
ScanStrategy 选择页面,默认扫描全部页面并支持提前退出Tj / TJ 这样的文本操作符,以及 Do 这样的图像操作符这套思路很像一个经验老到的检查员:不急着把整栋楼拆开重装,而是先看结构、查关键线索,再决定判断。
README 里提到,这个库可以在本地 200ms 以内处理文本型 PDF。与此同时,它还有一个重要的架构设计:文档只加载一次。
也就是说,检测和提取共享同一个已解析文档,不会为了 “先判断、再提取” 而重复做 I / O 和重复解析。这个细节非常工程化,也很能说明作者在意的不是孤立功能点,而是整条处理链的效率。
如果一个 PDF 工具只能把文字抠出来,那它离 “可用” 还差一步;真正难的是,把文本顺序、结构层级、列表、表格这些信息尽量保留下来。
pdf-inspector 在 README 里把这一块写得相当完整,它支持的并不是单纯文本输出,而是 带位置感知的提取,并进一步转换为 干净的 Markdown。
项目支持带有以下信息的文本提取:
这意味着它不是在 “把字符拼一遍”,而是在尽可能理解页面布局。尤其面对报纸式、多栏式排版时,这一点很重要。很多文档如果只按原始顺序硬拼,最后读起来会像把两条河流拧在了一起;而 pdf-inspector 明确支持 自动检测多栏布局,并按顺序组织阅读结果。
README 里列出了 Markdown 输出阶段会处理的内容,包括:
看起来像是一长串细节,但这些细节组合起来,其实是在回答同一个问题:导出的 Markdown 到底能不能直接读、直接用。
一份好的结构化提取,不是把页面 “打散”,而是把它 “翻译” 成另一种仍然有秩序的表达方式。pdf-inspector 做的就是这件事。
PDF 里的表格,向来是文档处理里的硬骨头。pdf-inspector 在 README 中把表格检测单独拿出来写,也说明这部分是它的重要能力之一。
它采用的是 双模式表格检测:
随后再做:
README 还特别提到,它可以处理:
也就是说,它考虑的不是 “有没有一张最标准的表”,而是现实里那些经常不那么整齐、甚至会跨页延续的表格。
这是一种很实在的设计态度:PDF 不是给解析器准备的,它原本是给人看的,所以所有结构化提取都天然带着一点 “逆向理解” 的意味。表格做得越认真,越说明项目没有停留在 “能跑通” 这个层面。
不少 PDF 处理问题,看起来像 “提取失败”,实际上根源常常藏在字体编码里。
README 里提到,pdf-inspector 支持:
同时,它还会自动检测 broken font encodings,并在发现问题时提示调用方回退到 OCR。
这一点很重要,因为 “错误地提取出一堆乱码” 往往比 “明确告诉你这里不可靠” 更糟糕。一个靠谱的系统,不只是会成功,也知道什么时候该承认自己不该继续硬提。
虽然它的核心是 Rust,但这个项目并没有把自己困在单一语言生态里。README 展示了它在多个环境里的使用方式:
这不是 “顺手做了几个绑定”,而是让同一个 PDF 能力,在不同上下文里都能被调用。
import pdf_inspector
result = pdf_inspector.process_pdf("document.pdf")
print(result.pdf_type) # "text_based", "scanned", "image_based", "mixed"
print(result.markdown) # Markdown string or None import { readFileSync } from 'fs';
import { processPdf, classifyPdf } from '@firecrawl/pdf-inspector';
const result = processPdf(readFileSync('document.pdf'));
console.log(result.pdfType); // "TextBased", "Scanned", "ImageBased", "Mixed"
console.log(result.markdown); // Markdown string or null import init, { processPdf } from '@firecrawl/pdf-inspector-wasm';
await init();
const response = await fetch('/document.pdf');
const pdf = new Uint8Array(await response.arrayBuffer());
const result = processPdf(pdf);
console.log(result.pdfType);
console.log(result.markdown); use pdf_inspector::process_pdf;
let result = process_pdf("document.pdf")?;
println!("Type: {:?}", result.pdf_type);
if let Some(markdown) = &result.markdown {
println!("{}", markdown);
} # Install the CLI tools
cargo install pdf-inspector
# Convert PDF to Markdown
pdf2md document.pdf
# JSON output (for piping)
pdf2md document.pdf --json
# Positioned TextItem JSON, including is_underline metadata
pdf2md document.pdf --items-json
# Raw markdown only (no headers)
pdf2md document.pdf --raw
# Token-efficient output (collapses long dot leaders and similar source padding)
pdf2md document.pdf --compact
# Insert page break markers (<!-- Page N -->)
pdf2md document.pdf --pages
# Process only specific pages
pdf2md document.pdf --select-pages 1,3,5-10
# Detection only (no extraction)
detect-pdf document.pdf
detect-pdf document.pdf --json
# Detection + layout analysis (tables, columns)
detect-pdf document.pdf --analyze --json从这些入口能看出来,pdf-inspector 的姿态很明确:它不只想成为一个库,也想成为一块可嵌入、可集成、可组合的基础能力。
WASM 版本在 README 里有几个很值得注意的点:
init() 之后提取是同步的这几条信息拼起来,勾勒出了一个很清晰的浏览器侧能力边界:它能在浏览器里本地做分类和结构化提取,但不会假装自己能替代图像 OCR。
这种边界感很可贵。很多工具最容易让人失望的地方,是宣传成 “全能型”,实际遇到复杂文档就沉默;而这里的说明方式更像一个稳重的工程组件 —— 能做什么,说清楚;不能替代什么,也说清楚。
napi/README.md 里提到一个主 README 没有重点展开、但非常实用的能力:按区域提取文本。
也就是:
extractTextInRegions(buffer: Buffer, pageRegions: PageRegions[]): PageRegionTexts[]它的设计目标也写得很明白:适用于 混合 OCR 流程。先由页面图像上的布局模型识别区域,再由这个函数从 PDF 结构层里提取对应区域的文本。
示例里每个区域结果都带有 needsOcr 标记;如果文本不可靠,比如:
那就把这个区域继续送 OCR。
这种能力很像是把 “整份 PDF 是否 OCR” 进一步细化成 “某一页、某一块区域是否 OCR”。对于复杂文档来说,这种粒度显然更灵活。
README 给了一张很完整的架构图,从原始 PDF bytes 开始,分成 detector 和 extractor 两条主线。
提取链路里又包括:
然后布局再进入:
最后经过分析、预处理、转换、分类和后处理,产出最终 Markdown。
这种结构感带来的最大好处,是让人一眼看出项目不是 “功能堆在一起”,而是按职责拆开的。README 还列出了源代码目录结构,例如:
lib.rs 负责公共 API、PdfOptions builder 和便捷函数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,也有对应选择。这样的设计很适合真实系统接入,因为不同业务场景对 “快” 和 “准” 的容忍度并不相同。
README 提供了一组基于 opendataloader-bench 语料库的 benchmark 结果,测试集包含 200 份 PDF,只展示本地引擎且关闭 OCR。
结果表里,pdf-inspector 的成绩是:
README 还写到,这组结果在 2026 年 7 月 31 日于 Apple M4 Pro 上刷新;速度统计是去掉预热后的五次完整跑分中位数。
项目还明确写出一条 “Best fit” 总结:它适合 native-text PDFs,尤其是在速度、阅读顺序和表格结构都重要的场景里。在这次对比中,它拿到了更高的整体分、阅读顺序分和表格分,同时也是最快的。
这段 benchmark 信息的价值,不只是 “我更强”,而是它把 “适合什么场景” 说清楚了:文本型 PDF、重视结构质量、同时在意速度。这和它前面的整体定位是连贯的。
如果你想从 README 给出的路径快速上手,不同环境的入口都非常明确。
pip install maturin
maturin develop --release npm install @firecrawl/pdf-inspector npm install @firecrawl/pdf-inspector-wasm cargo add pdf-inspector或者手动添加依赖:
[dependencies]
pdf-inspector = "0.1" cargo install wasm-pack --version 0.15.0 --locked
wasm-pack build wasm --target web --scope firecrawl --releaseNode.js 绑定的 README 里还特别提到,它提供预构建二进制,覆盖 Linux x64 / ARM64、macOS ARM64 和 Windows x64,npm 安装时只会安装与你平台匹配的那个包,而且 不需要 Rust toolchain。这对使用者来说非常友好,尤其是在只想集成能力、不想处理本地编译链路的时候。
读完 README 和 description,会很明显地感受到 pdf-inspector 的气质:它不是那种试图在文案里包办一切的项目,而是一块边界清楚、能力扎实、特别适合接进处理流水线的基础组件。
它关心的不是 “把 PDF 这件事说得多宏大”,而是几个很具体的问题:
这种专注会让一个工具显得很可靠。因为它不是对所有问题都说 “我能”,而是把自己最擅长的那一段路打磨得很深。
如果说 OCR 像重型机械,适合啃最硬的文档;那么 pdf-inspector 更像是一位懂分流、会判断、下手快的前场指挥。它先看看眼前这份 PDF 究竟是什么,再决定是轻快地本地处理,还是把真正难啃的部分交给后面的流程。
在 PDF 处理这条链路里,这样的角色,往往比想象中更重要。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。