项目简介
高性能 Rust 库,可检测 PDF 是否为文本型或扫描件,提取带位置信息的文本,并转换为干净的 Markdown,无需 OCR。提供 Python、Node.js 和 WebAssembly 绑定及 CLI 工具,用于智能路由,跳过大部分文本型 PDF 的 OCR 处理。
README 预览
# pdf-inspector\n\n[](https://crates.io/crates/pdf-inspector)\n[](https://www.npmjs.com/package/@firecrawl/pdf-inspector)\n[](https://pypi.org/project/pdf-inspector/)\n[](LICENSE)\n\nFast Rust library for PDF classification and text extraction. Detects whether a PDF is text-based or scanned, extracts text with position awareness, and converts to clean Markdown — all without OCR. Includes bindings for [Python](docs/python.md), [Node.js](napi/README.md), and [browser WebAssembly](wasm/README.md).\n\nBuilt by [Firecrawl](https://firecrawl.dev) to handle text-based PDFs locally in under 200ms, skipping expensive OCR services for the ~54% of PDFs that don't need them.\n\n## Features\n\n- **Smart classification** — Detect TextBased, Scanned, ImageBased, or Mixed PDFs in ~10-50ms by sampling content streams. Returns a confidence score (0.0-1.0) and per-page OCR routing.\n- **Text extraction** — Position-aware extraction with font info, X/Y coordinates, and automatic multi-column reading order.\n- **Markdown conversion** — Headings (H1-H4 via font size ratios), bullet/numbered/letter lists, code blocks (monospace font detection), tables (rectangle-based and heuristic), bold/italic formatting, URL linking, and page breaks.\n- **Table detection** — Dual-mode: rectangle-based detection from PDF drawing ops, plus heuristic detection from text alignment. Handles financial tables, footnotes, and continuation tables across pages.\n- **CID font support** — ToUnicode CMap decoding for Type0/Identity-H fonts, UTF-16BE, UTF-8, and Latin-1 encodings.\n- **Multi-column layout** — Automatic detection of newspaper-style columns, sequential reading order, and RTL text support.\n- **Encoding issue detection** — Automatically flags broken font encodings so callers can fall back to OCR.\n- **Single document load** — The document is parsed once and shared between detection and extraction, avoiding redundant I/O.\n- **Browser WebAssembly** — Run the same Rust parser locally in browsers and Web
常见问题 (5)
故障排除如何修复在 AWS Lambda 或 Amazon Linux 2023 上加载 @firecrawl/pdf-inspector 时出现的"GLIBC_2.35 not found"错误?
1.13.0–1.17.0 版本的 x64 GNU 二进制文件在构建时未使用 --use-napi-cross,因此需要 GLIBC_2.35,在 glibc 2.34 主机(如 Amazon Linux 2023、RHEL 9 和 Rocky Linux 9)上会运行失败。具有误导性的 Cannot find module 错误可能会掩盖 loadErrors 中真正的 GLIBC_2.35 not found 失败。
永久性的发布修复:在 .github/workflows/publish.yml 中使用 napi-cross 构建 x86_64-unknown-linux-gnu 目标:
- runner: blacksmith-8vcpu-ubuntu-2404
target: x86_64-unknown-linux-gnu
build-flags: --use-napi-cross
然后重新构建:
bunx napi build --platform --release \
--target x86_64-unknown-linux-gnu --use-napi-cross
验证生成的二进制文件:
objdump -T pdf-inspector.linux-x64-gnu.node \
| grep -o 'GLIBC_[0-9.]*' | sort -Vu | tail -1
# 预期输出:GLIBC_2.17
glibc 2.34 主机上的临时变通方案:将 x64 平台包固定到 1.12.0 版本(最后一个仅需 GLIBC_2.34 的发布版本),或者使用上述命令在本地重新构建的二进制文件。如果该包是作为传递依赖被引入的,可以使用 npm override:
{
"overrides": {
"@firecrawl/pdf-inspector-linux-x64-gnu": "1.12.0"
}
}
1.12.0 版本仍然需要 GLIBC_2.34,因此对 Debian 11 等较旧的系统没有帮助;这些系统需要重新构建的 GLIBC_2.17 二进制文件或修复后的发布版本。不要仅仅为了规避此问题而切换到 musl 构建。
故障排除如何修复在 pdf-inspector 中提取日语 CFF PDF 时显示重复且不相关汉字(例如"針針針")的问题?
出现此问题的原因是 try_remap_subset_cmap 对 CIDFontType0(CFF)字体应用了 TrueType 子集 GID 修复。在 CFF 字体中,CID 通过 CFF charset 进行解析,而不是重新编号的字形 ID,因此原始的 /ToUnicode CMap 不应被重新映射。
修复方法:升级到包含 PR fix(tounicode): skip subset GID remap for CIDFontType0 (CFF) descendants 的版本,或从已打补丁的源代码构建 pdf2md/pdf-inspector。
如果要自行修补 src/tounicode.rs,请在 try_remap_subset_cmap 中的 W 数组/顺序重映射检查之前添加一个守卫条件:
// Keep the existing ToUnicode CMap unchanged for CFF fonts.
if descendant_subtype == "CIDFontType0" {
return None;
}
保留现有的 remap_to_sequential 路径,使其仅对 CIDFontType2(TrueType)子代字体启用。然后重新构建:
cargo build --release
cargo run --bin pdf2md -- input.pdf > out.md
受影响的版本包括 @firecrawl/pdf-inspector 1.11.2 和 @firecrawl/pdf-inspector-wasm 0.1.3。作为临时解决方案,可以使用 pdfjs-dist 6.0.227 通过 getTextContent() 提取 PDF。请注意,这种损坏可能会生成有效但错误的汉字,因此统计 U+FFFD 替换字符的数量无法检测到该问题。
故障排除如何修复由经典 xref 条目使用裸 LF(19 字节步长)导致的 PDF 解析错误 "invalid file trailer"?
将 pdf-inspector 升级到包含 PR #508 的发布版本或构建版本。该修复可检测经典交叉引用表中条目以单个 LF 字节结尾的情况——即条目为 19 字节,而非规范要求的以 SP LF 结尾的 20 字节——并在原位置用符合规范的 SP LF 条目重建该表,同时保留文件的其余部分不变。这与 startxref 指针恢复是相互独立的。作为临时变通方案,可以使用 pypdf 等容错性较强的读取器重写 PDF,以重新生成 xref 表:
from pypdf import PdfReader, PdfWriter
reader = PdfReader("input.pdf")
writer = PdfWriter(clone_from=reader)
with open("fixed.pdf", "wb") as f:
writer.write(f)故障排除如何修复 pdf-inspector 从 PDF 中丢弃或错误转换西班牙语重音字母(例如 Cámara → CÆmara 和 código → cdigo)的问题?
这种情况发生在 pdf-inspector 1.20.0 中:当某个 standard-14 Type1 字体使用了诸如 << /BaseEncoding /WinAnsiEncoding >> 的编码字典、但没有 /Differences 数组时。pdf-inspector 会忽略 /BaseEncoding,转而按 StandardEncoding 解码文本,因此 0xC0–0xFF 范围内的字节会变成错误的字形或被丢弃。has_encoding_issues 仍保持为 false 同样也是一个 bug。
永久修复方案:将 pdf-inspector 升级到包含字体编码修复 "decode simple fonts through their base and built-in encodings and glyph names" 的版本,然后重新提取该文件。
临时解决方案:
1. 使用 pypdf 6.10 或更新版本进行提取,它能正确处理此编码:
pip install -U "pypdf>=6.10"from pypdf import PdfReader
reader = PdfReader("repro.pdf")
text = "\n".join(page.extract_text() for page in reader.pages)
print(text)
2. 或者,通过向已包含 /BaseEncoding 的编码字典添加空的 /Differences [] 数组来规范化该 PDF:
import pikepdf
with pikepdf.open("repro.pdf") as pdf:
for page in pdf.pages:
resources = page.get("/Resources")
if not isinstance(resources, pikepdf.Dictionary):
continue
fonts = resources.get("/Font")
if not isinstance(fonts, pikepdf.Dictionary):
continue
for font in fonts.values():
encoding = font.get("/Encoding")
if (
isinstance(encoding, pikepdf.Dictionary)
and "/Differences" not in encoding
):
encoding.Differences = pikepdf.Array()
pdf.save("repro-fixed.pdf")然后对
repro-fixed.pdf 运行 pdf-inspector。空的 /Differences 数组是合法的,它会强制受影响的版本去参考 /BaseEncoding /WinAnsiEncoding。故障排除为什么 extract_pages_markdown() 对含有少量原生文本页眉的扫描 PDF 页面报告 needs_ocr=False?
当页面大部分为整页栅格图像,但同时包含少量有效的原生文本(如页眉、印章或刊头)时,就会发生这种情况。旧版本的 extract_pages_markdown() 仅根据文本质量信号计算 needs_ocr,而不考虑图像覆盖率,因此可解码的页眉会导致页面被错误地标记为不需要 OCR。classify_pdf() 能够正确地将该页面检测为 image_based。
修复方法:将 pdf-inspector 升级到包含 issue #227 修复的版本,在该版本中,OCR 路由使用共享的分类逻辑,刊头覆盖在扫描图像之上的页面会被发送至 OCR:
pip install --upgrade pdf-inspector
受影响版本的变通方法:将 classify_pdf_bytes().pages_needing_ocr 视为权威依据,而不是逐页的 needs_ocr 字段:
import pdf_inspector
cls = pdf_inspector.classify_pdf_bytes(data)
ext = pdf_inspector.extract_pages_markdown_bytes(data)
pages_needing_ocr = set(cls.pages_needing_ocr)
for page_number, page in enumerate(ext.pages):
if page_number in pages_needing_ocr:
# Run OCR even if page.markdown is non-empty or page.needs_ocr is False.
ocr_text = run_ocr_for_page(data, page_number)
不要将提取出的非空 Markdown 作为页面不需要 OCR 的证据;它可能只包含原生页眉,而扫描的表格或正文内容已经缺失。