乐于分享
好东西不私藏

pdf-inspector:20ms判定PDF类型

pdf-inspector:20ms判定PDF类型

处理 PDF 是开发者最讨厌的工作之一——不是因为难,而是因为不可预测。同一个"PDF"可能是纯文本的(可以直接提取文字),也可能是扫描图片的(需要 OCR),还可能是混合的(部分页是文本、部分页是图片)。你得先判断类型再决定处理路径,而判断本身就需要解析整个文件。firecrawl/pdf-inspector 用纯 Rust 解决了这个问题:20ms 判定 PDF 类型,200ms 完成文本提取,日增 2500 星冲上 GitHub Trending。

一、PDF 处理的"黑箱问题":为什么现有工具都在猜

PDF 是一个出了名的复杂格式——ISO 32000 规范有近 1000 页,包含 700+ 种操作符。但 PDF 处理的核心痛点不是复杂度,而是不可预测性

开发者拿到一个 PDF 文件时,通常不知道它是"文本型"(内容以文字对象存储,可以直接提取)还是"扫描型"(内容是图片,需要 OCR)还是"混合型"(部分页文本、部分页扫描)。现有的处理流程是:先用 pdfplumber 或 PyPDF2 尝试提取文本 → 如果提取到的文本太少(比如每页不到 100 字符),判定为扫描型 → 启动 OCR 流程。

这个"先试再说"的流程有几个严重问题。第一,浪费时间——尝试提取文本本身需要解析整个 PDF,对于大文件可能需要数秒。第二,判断不准确——有些 PDF 虽然有文本对象,但文本是乱码(字体编码损坏),提取出来的是不可用的字符。第三,混合型 PDF 处理困难——前 10 页是文本、后 5 页是扫描,传统工具要么全部 OCR(慢),要么全部提取文本(后 5 页丢失)。

pdf-inspector 的解决思路是先分类再处理。它在 20ms 内完成 PDF 类型的精确判定——文本型、扫描型还是混合型,每一页都独立判定。然后根据分类结果路由到不同的处理管线:文本型直接提取(200ms),扫描型走 OCR(交给外部 OCR 服务),混合型按页分别处理。

二、核心能力:纯 Rust 实现的高性能 PDF 引擎

能力
性能
技术实现
传统工具对比
类型判定
20ms / 文件
解析 PDF 结构 + 分析字体/图像比例
PyPDF2 需 200-500ms
文本提取
200ms / 100 页
区域化文本提取 + 结构保留
pdfplumber 需 2-5 秒
Markdown 转换
300ms / 100 页
结构映射(标题/列表/表格 → MD 语法)
marker 需 5-10 秒
混合型检测
20ms / 文件
逐页分析文本密度 + 图像占比
传统工具无此能力
无 OCR 依赖
不需要 OCR 服务
纯文本提取不需要外部依赖
传统工具依赖 Tesseract

20ms 判定的技术原理: pdf-inspector 不需要完整解析 PDF 的所有内容。它只解析 PDF 的结构元数据——字体表(Font Dictionary)、图像资源表(XObject Resources)和页面树(Page Tree)。通过分析每个页面的字体数量和图像占比,可以在不读取页面内容的情况下判定类型。如果一页有文本字体但没有大图像,判定为文本型;如果一页有大图像但没有文本字体,判定为扫描型;如果两者都有,判定为混合型。

纯 Rust 实现的意义: pdf-inspector 不是 Python 库的 Rust 封装——它从零用 Rust 重写了 PDF 解析引擎。这意味着它不需要 Python 运行时、不需要 C 扩展、没有 GIL 限制。对于需要高并发处理大量 PDF 的服务端场景(如文档管理系统、知识库构建),Rust 的零成本抽象和线程安全特性意味着可以轻松实现并行处理。通过 napi-rs 技术,pdf-inspector 也提供了 Node.js/Bun 绑定,可以在 JavaScript 项目中直接调用。

三、从安装到实战:3 步处理 PDF

第 1 步:安装

# Rust 项目安装cargo add pdf-inspector# Node.js / Bun 项目安装npm install @firecrawl/pdf-inspector# Python(基于 PyO3 原生绑定)pip install pdf-inspector-python

第 2 步:判定 PDF 类型

use pdf_inspector::PdfInspector;fnmain() -> anyhow::Result<()> {    let inspector = PdfInspector::new("document.pdf")?;    let classification = inspector.classify()?;    for page in &classification.pages {        println!(            "Page {}: {:?} (text_density: {:.2}, image_ratio: {:.2})",            page.number, page.page_type, page.text_density, page.image_ratio        );    }    Ok(())}/*输出示例:Page 1: Text (text_density: 0.85, image_ratio: 0.02)Page 2: Scanned (text_density: 0.01, image_ratio: 0.95)Page 3: Mixed (text_density: 0.45, image_ratio: 0.40)*/

第 3 步:提取文本并转为 Markdown

use pdf_inspector::PdfInspector;fnmain() -> anyhow::Result<()> {    let inspector = PdfInspector::new("document.pdf")?;    let classification = inspector.classify()?;    // 提取文本并转为 Markdown,自动跳过扫描图片页    let markdown = inspector.extract_text_to_markdown(        pages: None,        // None 代表处理全部页面        skip_scannedtrue, // 过滤 Scanned 扫描页,只保留纯文字/图文混合页    )?;    println!("{}", markdown);    Ok(())}/*输出结构化 Markdown 示例:# 第一章 项目概述本文档介绍了...## 1.1 技术架构...*/

进阶:混合型 PDF 的智能路由

use pdf_inspector::{PdfInspectorPageType};// 模拟OCR服务、文本处理、文本合并工具struct OcrService;impl OcrService {    async fn recognize(&self, imgVec<u8>) -> anyhow::Result<String> {        Ok("OCR识别文本".to_string())    }}fn process_text(content: String) {}fn merge_text(aStringbString) -> String {    format!("{}\n{}", a, b)}#[tokio::main]async fn main() -> anyhow::Result<()> {    let ocr_service = OcrService::new();    let inspector = PdfInspector::new("document.pdf")?;    let classification = inspector.classify()?;    // 对混合型 PDF 逐页分类处理    for page in &classification.pages {        match page.page_type {            // 纯文字页面:直接提取原生文本            PageType::Text => {                let text = inspector.extract_page_text(page.number)?;                process_text(text);            }            // 纯扫描图片页:提取图片 + OCR 识别            PageType::Scanned => {                let image = inspector.extract_page_image(page.number)?;                let ocr_result = ocr_service.recognize(image).await?;                process_text(ocr_result);            }            // 图文混合页:原生文本 + 图片OCR 结果合并            PageType::Mixed => {                let text = inspector.extract_page_text(page.number)?;                let image = inspector.extract_page_image(page.number)?;                let ocr_result = ocr_service.recognize(image).await?;                // 合并原生文本与图片识别内容                let full_content = merge_text(text, ocr_result);                process_text(full_content);            }        }    }    Ok(())}

四、pdf-inspector vs PyPDF2 vs marker vs Unstructured

维度
pdf-inspector
PyPDF2
marker
Unstructured
语言
Rust(原生性能)
Python
Python + PyTorch
Python
类型判定
20ms 精确判定
无(需手动尝试)
有(但慢)
文本提取
200ms / 100 页
2-5 秒 / 100 页
5-10 秒 / 100 页
3-8 秒 / 100 页
Markdown 转换
内置
内置(基于 ML)
内置
混合型支持
逐页检测
有但需配置
OCR 集成
不内置(可外接)
内置(Tesseract)
内置
依赖大小
<5MB(纯 Rust)
~50MB
~2GB(含 PyTorch)
~200MB
适合场景
高并发服务端、RAG 前端
简单文本提取
复杂文档结构还原
通用文档处理

pdf-inspector 和 marker 的本质差异在于定位。marker 用机器学习模型理解文档结构(识别标题、段落、表格、图片的位置和关系),输出高质量的 Markdown——但它依赖 PyTorch,运行慢(5-10 秒/100 页),适合"一次性转换"。pdf-inspector 用规则引擎做快速分类和提取,不做深度结构理解——但速度快(200ms/100 页),适合"实时处理"和"高并发场景"。

五、3 个深度实战场景

场景 1:RAG 知识库的 PDF 预处理管线

企业有 10,000 个 PDF 文档需要导入 RAG 知识库。传统流程:用 PyPDF2 提取文本 → 对提取结果做质量检查 → 质量差的走 OCR → 切分 Chunk → 向量化。整个流程每文档平均 15 秒,10,000 个文档需要 41 小时。

用 pdf-inspector 优化:先用 pdf-inspector 在 20ms 内判定每个 PDF 的类型 → 文本型直接提取(200ms)→ 扫描型走 OCR(3-5 秒)→ 混合型按页分别处理。同时用 Rust 的多线程并行处理 16 个文档。10,000 个文档从 41 小时降到 2 小时——20 倍加速。

场景 2:实时文档分析 API

SaaS 产品需要提供"上传 PDF → 实时分析"的功能。用户上传一个 50 页的 PDF,系统需要在 3 秒内返回分析结果。用 PyPDF2 需要 5 秒(超出 SLA),用 marker 需要 15 秒(完全不可用)。用 pdf-inspector:20ms 判定 + 200ms 提取 = 220ms,加上 LLM 分析的 1-2 秒,总耗时约 2 秒——在 SLA 内。

场景 3:大规模 PDF 归档分类

政府机构有 500,000 份历史 PDF 档案,需要按"文本可搜索"和"需 OCR"分类。传统方式是逐个打开检查,每份 10 秒,需要 58 天。用 pdf-inspector 的 20ms 判定 + 16 线程并行:500,000 × 20ms ÷ 16 = 625 秒,约 10 分钟。

六、技术边界、生态定位与区域化提取

pdf-inspector 的核心局限是不做深度结构理解。它能判定"这页是文本型还是扫描型",但不能理解"这段文本是标题还是正文"。对于需要精确还原文档结构(如学术论文的章节层级、表格的行列关系)的场景,pdf-inspector 的输出需要后处理。

另一个局限是加密 PDF 的处理。pdf-inspector 目前不支持密码保护的 PDF——它会在分类阶段直接返回错误。这是 Firecrawl 团队标注的"已知限制",预计在后续版本中支持。

OCR 的缺失是另一个讨论点。pdf-inspector 刻意不内置 OCR——它的定位是"快速分类和文本提取",OCR 交给专业工具(Tesseract、PaddleOCR、云 OCR 服务)。这种"做一件事并做好"的设计哲学是合理的——但用户需要自己搭建 OCR 管线来处理扫描型 PDF。

理解 pdf-inspector 的战略意义,需要把它放在 Firecrawl 生态中看。 Firecrawl 是一个网页抓取和转换平台——把任意网页转为干净的 Markdown,供 LLM 使用。pdf-inspector 是 Firecrawl 向 PDF 领域的延伸——当 Firecrawl 的用户说"我不仅需要抓网页,还需要处理 PDF",pdf-inspector 就是回应。

Firecrawl 选择用 Rust 而非 Python 重写 PDF 处理引擎,这个决策背后的逻辑是:Firecrawl 的核心场景是"高并发服务端"——它需要同时处理数千个抓取请求,每个请求的延迟都要控制在数百毫秒内。Python 的 GIL 限制了并发能力,而 Rust 的零成本抽象和线程安全让它可以轻松实现高并发。pdf-inspector 的 20ms 分类和 200ms 提取,在 Rust 中是可实现的,在 Python 中几乎不可能。

AGPL-3.0 协议的选择也值得关注。Firecrawl 的核心产品是云服务(SaaS),开源代码用 AGPL 协议意味着:如果你修改了 pdf-inspector 并在自己的服务中使用,你必须开源你的修改。这个协议阻止了云厂商直接拿 pdf-inspector 做商业化竞品,同时允许开发者在本地使用。这是"开源核心 + 云服务变现"模式的标准做法。

pdf-inspector 的文本提取也不是简单的"把 PDF 里的文字读出来"——它使用区域化提取(Region-based Extraction)技术。 PDF 的每一页被划分为多个区域:文本区域、图像区域、表格区域、页眉页脚区域。每个区域独立提取,然后按照阅读顺序(通常是 Z 字形扫描)组合成结构化的文本。

这种区域化提取的优势在于结构保留。当 pdf-inspector 输出 Markdown 时,它能正确识别标题层级(H1/H2/H3)、列表项(有序/无序)、表格行列关系。这不需要机器学习模型——它完全基于 PDF 的结构化操作符(BT/ET 文本块、Tm 矩阵变换、Td 位置偏移)来推断布局结构。这种规则驱动的结构识别在标准格式的文档(论文、报告、合同)上准确率超过 95%,但在非标准格式(艺术设计稿、手写笔记扫描)上效果有限。这意味着什么?意味着 pdf-inspector 在企业文档处理场景中已经足够可靠,但在创意设计领域还需要配合更强大的结构理解工具。对于 RAG 知识库构建这个核心场景,95% 的准确率已经达到了生产级可用标准。

对产业意味着什么

PDF 处理是 RAG(检索增强生成)系统的第一公里——如果 PDF 提取的文本质量差,后面的向量化、检索、生成都会受影响。pdf-inspector 用纯 Rust 重写了这条"第一公里"的基础设施,把处理速度提升了一个数量级。当 PDF 预处理从"小时级"降到"分钟级",RAG 系统的迭代速度也会随之提升。

配套资源

• 项目地址:https://github.com/firecrawl/pdf-inspector

• NPM 包:https://www.npmjs.com/package/@firecrawl/pdf-inspector

• 协议:AGPL-3.0 开源

3 件事收尾

用 pdf-inspector 处理你手头 10 个不同类型的 PDF(文本型、扫描型、混合型),查看分类报告——理解"20ms 判定"的实际效果

对比 pdf-inspector 和 PyPDF2 在同一批 PDF 上的提取速度——性能差异在高并发场景下会被放大

如果你正在构建 RAG 管线,用 pdf-inspector 替换 PDF 预处理环节,测量端到端延时的改善——"第一公里"的提速会传导到整个管线

项目地址:https://github.com/firecrawl/pdf-inspector

协议:AGPL-3.0 开源