做文档处理的人都知道,PDF是个让人又爱又恨的格式。爱的是它跨平台、排版固定,恨的是想把里面的内容掏出来的时候,各种幺蛾子都来了。
最头疼的就是OCR。一提到PDF转文字,很多人第一反应就是上OCR,不管三七二十一,先识别一遍再说。但实际统计下来,大概有54%的PDF本身就是文本型的——也就是说,里面的文字本来就是可选的,根本不需要OCR去“认”。这些PDF大多是报告、论文、发票、合同之类的,直接从Word或LaTeX生成的。
把这种PDF扔给OCR,就像拿扫描仪去复印一份电子文档——多此一举不说,还费钱费时间。
Firecrawl 最近开源了一个库,叫 pdf-inspector,专门解决这个问题。纯Rust写的,能快速判断一个PDF是文本型还是扫描型,然后直接提取文字、转成Markdown,整个过程在本地跑,200毫秒以内。
它到底能干什么
简单说,pdf-inspector 干四件事:
分类——告诉你这个PDF是文本型、扫描型、纯图片型还是混合型,还带置信度。 提取——把文字捞出来,带位置坐标、字体信息,自动处理多栏排版和阅读顺序。 转Markdown——标题、列表、表格、代码块、加粗斜体、超链接,都能转,排版整洁。 多语言绑定——Python、Node.js、浏览器(WASM)都能直接用,不用自己编译。
最核心的设计理念是:能本地搞定的,绝不请外援。OCR服务按页收费,延迟还高,为什么不用一个轻量级的本地解析器先把能处理的处理掉?
跑得有多快?
官方在 Apple M4 Pro 上跑了一组对比测试,用的是 OpenDataLoader-Bench 的200个PDF数据集,只对比本地解析引擎,不涉及OCR。
| pdf-inspector | 0.875 | 0.915 | 0.814 | 0.470s | |
pdf-inspector 在综合得分、阅读顺序和表格识别上都跑到了第一,速度更是碾压——处理200个文档只用了0.47秒,比第二快的LiteParse还快将近一倍。PyMuPDF4LLM和MarkItDown虽然也能用,但速度差了整整一个数量级。
注意,这个速度是完整处理200个文档的中位数耗时,包含了解析、分类、提取、转Markdown的全部流程。
怎么用?
安装方式取决于你的技术栈。
Python
先用 maturin 编译(因为是Rust写的,需要编译成Python扩展):
pip install maturinmaturin develop --release然后直接用:
import pdf_inspectorresult = pdf_inspector.process_pdf("document.pdf")print(result.pdf_type) # "text_based", "scanned", "image_based", "mixed"print(result.markdown) # Markdown字符串Node.js
直接 npm 装:
npm install @firecrawl/pdf-inspectorimport { readFileSync } from'fs';import { processPdf } from'@firecrawl/pdf-inspector';const result = processPdf(readFileSync('document.pdf'));console.log(result.pdfType); // "TextBased", "Scanned", ...console.log(result.markdown);浏览器(WASM)
同样 npm 安装,然后异步加载:
npm install @firecrawl/pdf-inspector-wasmimport init, { processPdf } from'@firecrawl/pdf-inspector-wasm';await init();const response = await fetch('/document.pdf');const pdf = newUint8Array(await response.arrayBuffer());const result = processPdf(pdf);Rust(直接用)
Cargo 加依赖:
[dependencies]pdf-inspector = "0.1"use pdf_inspector::process_pdf;let result = process_pdf("document.pdf")?;println!("{:?}", result.pdf_type);ifletSome(md) = result.markdown {println!("{}", md);}命令行工具
安装 CLI:
cargo install pdf-inspector转换 PDF 到 Markdown:
pdf2md document.pdf# 输出JSONpdf2md document.pdf --json# 只检测不提取detect-pdf document.pdf# 检测+布局分析detect-pdf document.pdf --analyze --json分类是怎么做的?
很多人好奇它怎么判断一个PDF是不是扫描件。原理不复杂:
解析PDF的页面树和交叉引用表(xref),不加载全部对象。 扫描内容流,找两类操作符: Tj/TJ(文字绘制)和Do(图片绘制)。根据文字操作符的出现情况,决定PDF类型。
默认策略是“EarlyExit”——扫描所有页面,但一旦遇到非文字页面就立即停止,这样大多数纯文本PDF只需要扫头几页就能判断,300页的文档也只要几十毫秒。
如果你需要更精确的混合类型判断,可以换成“Full”模式,扫描所有页面;或者“Sample(n)”模式,只抽样n页,适合超大文件。
分类结果里还带了一个 pages_needing_ocr 字段,告诉你具体哪几页缺文字,这样就可以只对那几页做OCR,而不是整本全部识别。
Markdown转得怎么样?
文字提取出来之后,转换器会做一堆精细处理:
标题:根据字体大小相对比值,自动分 H1-H4,聚类阈值0.5pt。 列表:支持 •、-、*项目符号,数字序号(1. 1) (1)),字母序号(a. a) (a))。代码块:检测到 Courier、Consolas、Fira Code 这类等宽字体,自动包成代码块。 表格:两种方式检测——一种是PDF绘图指令里的矩形框(用并查集聚合成表格),另一种是根据文字对齐规律启发式判断。金融报表里那种一坨数字挤在一起的,也能拆开。 加粗/斜体:通过字体名称里的 Bold、Italic、Oblique 判断。 超链接:转成 Markdown 的 [text](url)。连字符:自动把跨行断开的单词接回去。 页码和目录点:自动过滤页码,目录里的“........”缩成一个省略号。 页眉页脚:暂未专门处理,但提供了页面分割标记 <!-- Page N -->供你自己过滤。
表格这块值得多说一句。PDF里的表格没有“表格”这个语义概念,都是画线加文字拼出来的。pdf-inspector 用了双模检测:矩形检测模式从绘图操作里找表格框线,启发式模式从文字坐标对齐关系推断表格结构。实测对财务表格、带脚注的复杂表格、跨页连续表格都能处理。
字体编码也管
PDF里最烦人的问题之一是 CID 字体编码。很多 PDF 用的 Type0/Identity-H 字体,ToUnicode CMap 没嵌全,提取出来就是乱码。
pdf-inspector 内置了 ToUnicode CMap 解析,支持 UTF-16BE、UTF-8、Latin-1 回退,还带了一个 Adobe Glyph List 的映射表。遇到实在解不出的编码,会自动打上“encoding issue”标记,让你在业务层决定要不要交给 OCR 兜底。
架构长什么样?
整个库分两层:
detector:快速分类,只扫内容流,不建完整文档对象。 extractor:真正干活,加载字体、遍历操作符、抽文字和矩形、处理 XObject 和链接,最后走 layout 流水线——列检测→行分组→阅读顺序→表格识别→Markdown 生成。
文档只加载一次,分类和提取共用同一个 Document 对象,避免重复 I/O。
项目结构很清晰,Rust 核心在 src/,Python 绑定在 python.rs,Node 绑定在 napi/,WASM 在 wasm/,CLI 在 bin/。想自己改也方便。
适合什么场景?
pdf-inspector 最适合那些文本型 PDF 占主流、对速度和结构化要求高的场景:
RAG 流水线:先分类,文本型直接走本地提取,扫描型才调 OCR,节省大量 API 费用。 财报、研报、法律文书:表格多、排版复杂,需要保留结构和阅读顺序。 批量文档归档:把大量 PDF 转成 Markdown 入库,方便检索。 浏览器端预览:WASM 版本可以直接在网页里解析 PDF,不需要上传服务器,保护隐私。
顺便说一句,这个库是 Firecrawl 在维护,就是那个做网页抓取和 AI 数据处理的公司,他们内部已经在生产环境用了很久,可靠性是有保障的。
项目仓库在 GitHub 上搜 firecrawl/pdf-inspector,文档很全,Python、Node、Rust、WASM 的 API 参考都有。测过的可以拿自己的 PDF 跑跑看,看看分类准不准,速度够不够。
PDF 处理这个赛道,卷完 OCR 卷布局,卷完布局卷速度,pdf-inspector 至少在这三个方向上都给出了一个不错的答案。下次再遇到 PDF,记得先问问它——这玩意儿是不是本来就是文字?
夜雨聆风