pdf-inspector 研究筆記¶
資料來源¶
| 項目 | 連結 |
|---|---|
| GitHub Repo | https://github.com/firecrawl/pdf-inspector |
| 官方文件站 | https://firecrawl.github.io/pdf-inspector/ |
| crates.io | https://crates.io/crates/pdf-inspector |
| npm | https://www.npmjs.com/package/@firecrawl/pdf-inspector |
| PyPI | https://pypi.org/project/pdf-inspector/ |
| Firecrawl 部落格:Fire-PDF | https://www.firecrawl.dev/blog/fire-pdf-launch |
| Firecrawl 部落格:/parse | https://www.firecrawl.dev/blog/introducing-parse |
| Benchmark 語料庫 | https://github.com/opendataloader-project/opendataloader-bench |
Metadata(擷取於 2026-08-03):6,529 ★、Rust(含 Python/JS/WASM 綁定)、MIT License、最新版
0.2.6。由 Firecrawl 維護,是其/parse文件解析服務的底層引擎之一。
專案概述¶
pdf-inspector 是一個用 Rust 寫的高速 PDF 解析函式庫,做三件事:分類(classification)、位置感知文字擷取(text extraction)、以及轉乾淨 Markdown——而且完全不呼叫 OCR。
它的核心命題來自一個觀察:在真實世界的 PDF 流水線中,約 54% 的 PDF 本來就是文字型(text-based),根本不需要 OCR。既有做法卻常常無腦把每一份 PDF 都丟進昂貴又慢(2–10 秒/份)的 OCR 服務。pdf-inspector 的定位就是流水線最前端的「分流器」:先花 ~10–50ms 判斷這份 PDF 到底是文字型還是掃描型,文字型就地在 200ms 內解析完,掃描型才轉送 OCR,藉此把大多數文件的成本與延遲砍掉。
它刻意走「輕量」路線:純 Rust、沒有 ML 模型、沒有外部服務,PDF 解析只依賴單一套件 lopdf。這讓它可以編成 ~5–6MB 的原生 binary,甚至能編成 WebAssembly 在瀏覽器 / Web Worker 裡跑,不需要任何 server round trip。適合報告、論文、財報、發票、法律文件這類「原生文字 PDF」的結構化擷取場景。
核心功能¶
| 功能 | 說明 |
|---|---|
| 智慧分類 | 抽樣 content stream,在 ~10–50ms 內判斷 TextBased / Scanned / ImageBased / Mixed,回傳 0.0–1.0 信心分數,並給出逐頁 OCR 路由(pages_needing_ocr) |
| 位置感知文字擷取 | 帶字型資訊、X/Y 座標,自動處理多欄(newspaper-style)閱讀順序與 RTL |
| Markdown 轉換 | H1–H4 標題(依字級比例)、項目/數字/字母清單、程式碼區塊(等寬字偵測)、表格、粗斜體、URL 連結、分頁標記 |
| 表格偵測 | 雙模式:從 PDF 繪圖指令的矩形偵測(union-find)+ 從文字對齊的啟發式偵測;能處理財報表格、註腳、跨頁續表 |
| CID 字型支援 | Type0/Identity-H 字型的 ToUnicode CMap 解碼,UTF-16BE / UTF-8 / Latin-1 編碼 |
| 編碼問題偵測 | 自動標記壞掉的字型編碼,讓呼叫端可以 fallback 到 OCR |
| 單次載入 | 文件只 parse 一次,偵測與擷取兩階段共用,避免重複 I/O |
| 瀏覽器 WASM | 同一份 Rust parser 直接在瀏覽器本地跑,內嵌 CMap |
分類是怎麼運作的¶
- 解析 xref table 與 page tree(不做完整物件載入)
- 依
ScanStrategy選頁(預設掃全部、遇非文字頁 early exit) - 在 content stream 找
Tj/TJ(文字運算子)與Do(影像運算子) - 依「抽樣頁面是否有文字運算子」分類
因為不做完整載入,300+ 頁的 PDF 也能在毫秒級判完。結果附帶 pages_needing_ocr 清單,達成逐頁 OCR 路由,而非全有全無。
ScanStrategy 策略:
| 策略 | 行為 | 適用 |
|---|---|---|
EarlyExit(預設) |
掃全部、遇第一個非文字頁就停 | 只想把 TextBased 分流到快速擷取的流水線 |
Full |
掃全部、不 early exit | 需要準確區分 Mixed vs Scanned |
Sample(n) |
均勻抽 n 頁(首/尾/中) | 超大 PDF,速度優先於精度 |
Pages(vec) |
只掃指定頁碼 | 呼叫端已知要檢查哪幾頁 |
處理管線架構¶
PDF bytes
├─► detector → PdfType(TextBased / Scanned / ImageBased / Mixed)
└─► extractor
├─ fonts → 字寬、編碼
├─ content_stream → 走訪 PDF 運算子 → TextItems + PdfRects
├─ xobjects → Form XObject 文字、影像佔位
├─ links → 超連結、AcroForm 欄位
└─ layout → 欄位偵測 → 分行 → 閱讀順序
├─► tables → 矩形偵測 / 啟發式偵測 / 格線 / 格式化
└─► markdown → 分析 → 前處理 → 轉換 → 分類 → 後處理
文件透過 load_document_from_path / load_document_from_mem 只載入一次,偵測與擷取共用,沒有重複 parsing。
Benchmark¶
在 opendataloader-bench 語料庫(200 份 PDF)上評測,僅比較不含 model-based 解析的本地引擎,且關閉 OCR。分數 0–1,越高越好(更新於 2026-07-31,Apple M4 Pro):
| 引擎 | 總分 | 閱讀順序 (NID) | 表格 (TEDS) | 標題 (MHS) | 速度(200 份) |
|---|---|---|---|---|---|
| pdf-inspector | 0.875 | 0.915 | 0.814 | 0.788 | 0.470s |
| liteparse | 0.873 | 0.913 | 0.693 | 0.811 | 0.750s |
| opendataloader | 0.831 | 0.902 | 0.489 | 0.739 | 2.569s |
| pymupdf4llm | 0.735 | 0.886 | 0.401 | 0.424 | 17.117s |
| markitdown | 0.589 | 0.844 | 0.273 | 0.000 | 16.165s |
重點:總分、閱讀順序、表格三項第一,且是全場最快(比 pymupdf4llm 快約 36 倍、比 markitdown 快約 34 倍)。標題偵測略輸 liteparse。對「原生文字 PDF 要乾淨結構化 Markdown」這個場景,是很強的本地預設選項。
快速開始¶
# Rust
cargo add pdf-inspector
# Python(需 maturin 從原始碼建置)
pip install maturin && maturin develop --release
# Node.js
npm install @firecrawl/pdf-inspector
# 瀏覽器 WASM
npm install @firecrawl/pdf-inspector-wasm
import pdf_inspector
result = pdf_inspector.process_pdf("document.pdf")
print(result.pdf_type) # "text_based" / "scanned" / "image_based" / "mixed"
print(result.markdown) # Markdown 字串或 None
CLI(cargo install pdf-inspector):
pdf2md document.pdf # PDF → Markdown
pdf2md document.pdf --json # JSON 輸出
pdf2md document.pdf --compact # 省 token(收合長 dot leaders)
pdf2md document.pdf --select-pages 1,3,5-10
detect-pdf document.pdf --analyze --json # 只偵測 + 版面分析
--compact 這種「省 token」旗標很值得注意:它是為了餵給 LLM 而設計的,會把 TOC 的長串點點、來源 padding 收成 " ... "。
目前限制 / 注意事項¶
- 不做 OCR:這是設計取捨而非缺陷。掃描型 / 影像型 PDF 它只負責「認出來並標記」,實際文字還是得靠外部 OCR。它是分流器,不是全能解析器。
- 標題偵測靠字級啟發式:H1–H4 依字級比例+0.5pt 分群推斷,粗斜體靠字型名稱樣式(Bold/Italic/Oblique)。遇到不照排版慣例的 PDF 可能誤判(benchmark 上標題分數也確實略輸 liteparse)。
- Python 需自行建置:PyPI 有套件,但 README 的 Python 範例走
maturin develop,跨平台預編譯輪子的完整度需自行確認。 - 表格是啟發式:矩形+對齊雙模式已經很強,但複雜跨頁 / 合併儲存格表格仍是 PDF 解析的通病難題。
- 版本尚在
0.x(0.2.6),API 可能仍會變動。
研究價值與啟示¶
關鍵洞察¶
-
「先分類再決定要不要花大錢」是被低估的架構槓桿。 這個專案最聰明的地方不是解析本身,而是那個 ~20ms 的前置分流:與其優化 OCR,不如先判斷「這份根本不用 OCR」。用便宜的判斷擋掉昂貴的處理,是所有 AI 流水線都適用的成本控制心法。
-
逐頁 OCR 路由 > 全有全無。
pages_needing_ocr讓 Mixed 型 PDF(例如前面是文字報告、附錄夾了掃描圖)可以只把該掃描的幾頁送 OCR。這種 fine-grained routing 是把成本再壓一階的關鍵,很多解析器缺這一層。 -
「不用 ML 模型」反而是賣點。 在人人都上大模型的年代,它反其道而行——純 Rust、單一依賴、5–6MB binary、可編 WASM 在瀏覽器本地跑。對延遲敏感、要地端部署、或不想把文件外送第三方的場景(法律、財報、隱私資料),這種零外部依賴的輕量方案有結構性優勢。
-
速度本身就是能力,不只是體驗。 200 份文件 0.47 秒(比 pymupdf4llm 快 ~36 倍)意味著它可以放在「同步請求路徑」上即時判斷,而 16–17 秒級的解析器只能走非同步佇列。速度差一個數量級,架構位置就完全不同。
-
為 LLM 而生的細節。
--compact收合 dot leaders、分頁標記、乾淨 Markdown 輸出——這些都不是傳統 PDF 工具會在意的,而是「輸出要餵給 LLM」時才會冒出來的需求。它顯示 PDF 解析器正在從「給人看」重新定位成「給模型吃」。
與其他專案的關聯¶
- OpenDataLoader PDF:直接對照組。兩者都是開源 PDF→Markdown 解析器、都在 opendataloader-bench 上評測。差別在——OpenDataLoader 是 Java 生態、主打 LangChain 整合;pdf-inspector 是 Rust、主打速度+免 OCR 分流,且在同一 benchmark 上總分(0.875 vs 0.831)與速度(0.47s vs 2.57s)都勝出。想做 RAG 前處理的人值得把兩者放一起比。
- RAG 流水線:它解決的是 RAG「文件攝取(ingestion)」階段最髒的一段——把任意 PDF 變成乾淨、有結構、省 token 的 Markdown。可視為 RAG chunking 之前的必要前處理層。
- Firecrawl
/parse與 Fire-PDF:pdf-inspector 是 Firecrawl 商業文件解析服務的開源底層引擎;/parse在它之上再疊自家 OCR 模型(宣稱 0.002s/頁)。這是典型的「開源引擎 + 商業加值層」商業模式——核心開源換取採用與信任,OCR/雲端服務變現。
研究日期:2026-08-03。Metadata(★ 數、版本)為擷取當下數值,可能隨時間變動。