当两个 npm 包争夺 pdfjs-dist 时:转向系统二进制文件
Source: Dev.to
请提供您希望翻译的具体文本内容,我将为您翻译成简体中文。
问题
我正在为一个 Next.js 应用添加对扫描 PDF 的 OCR 支持。计划很简单:
- 使用 pdf-to-img 将 PDF 页面栅格化。
- 将图像管道传输给 Tesseract。
- 连接提取的文本。
在安装 pdf-to-img 并部署到预生产环境后,上传扫描的 PDF 时出现以下错误:
Error: API version does not match Worker version为什么会发生
项目已经使用 unpdf 从数字 PDF 中提取文本。pdf-to-img 和 unpdf 都捆绑了各自的 pdfjs-dist 副本,但它们使用的版本不同:
| 包 | 捆绑的 pdfjs‑dist 版本 |
|---|---|
| pdf-to-img | ~5.4.624 |
| unpdf | ~5.4.296 |
当两个包在同一个 Node.js 进程中加载时,每个都会尝试注册自己的 PDF.js worker。worker 之间冲突,导致 PDF.js 抛出 “API version does not match Worker version” 错误。由于这些捆绑的副本不是 peer 依赖,npm 去重无法解决冲突。
Dead‑End Alternatives
我探索了其他基于 JavaScript 的 PDF‑to‑image 解决方案:
- pdfjs-dist 直接使用(仍被锁定在
unpdf所需的版本) - canvas + 手动 PDF.js 渲染(需要本地绑定和复杂的 Docker 设置)
- sharp(无法光栅化 PDF)
- pdf-poppler(维护不佳的包装器)
所有这些要么重新引入了相同的 pdfjs‑dist 冲突,要么需要繁重的本地构建,或者已经被放弃。
更佳方案:使用系统二进制文件
将 PDF 转换为图像并执行 OCR 的任务在操作系统层面已经得到解决。像 poppler-utils(pdftoppm)和 tesseract-ocr 这样的工具稳定、快速且经过实战检验。
安装二进制文件
RUN apt-get update && apt-get install -y \
poppler-utils \
tesseract-ocr \
tesseract-ocr-eng \
&& rm -rf /var/lib/apt/lists/*OCR 流程实现
import { execSync } from "child_process";
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
async function ocrScannedPdf(pdfPath: string): Promise {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ocr-"));
const outputPrefix = path.join(tmpDir, "page");
try {
// 将 PDF 页面转换为 PNG 图像(300 DPI,适合 OCR 精度)
execSync(`pdftoppm -png -r 300 "${pdfPath}" "${outputPrefix}"`, {
timeout: 60000,
});
// 收集生成的图像文件
const images = fs
.readdirSync(tmpDir)
.filter((f) => f.endsWith(".png"))
.sort()
.map((f) => path.join(tmpDir, f));
if (images.length === 0) {
throw new Error("pdftoppm produced no output");
}
// 对每一页运行 Tesseract
const texts = images.map((imgPath) => {
const result = execSync(`tesseract "${imgPath}" stdout -l eng`, {
timeout: 30000,
});
return result.toString().trim();
});
return texts.filter(Boolean).join("\n\n");
} finally {
// 清理临时文件
fs.rmSync(tmpDir, { recursive: true, force: true });
}
}关键要点
- 转换或 OCR 步骤不需要任何 npm 包。
- 不会出现版本冲突,因为系统二进制文件独立于 Node.js 模块。
- 整个流程约 20 行代码即可完成。
为什么更倾向于使用系统二进制文件而不是 npm 包装器
当 npm 包仅仅是对系统二进制文件(例如 ImageMagick、FFmpeg、Ghostscript、Poppler、Tesseract、wkhtmltopdf)进行包装时:
- 检查维护情况 – 包装器是否得到良好维护,还是仅仅是一个薄层包装?
- 留意传递冲突 – 包装器是否捆绑了自己的库副本,可能与其他依赖产生冲突?
- 考虑 Docker 简洁性 – 直接安装二进制文件通常能得到更简洁的 Dockerfile。
npm 生态系统在纯 JavaScript 问题上表现出色。对于那些已有长期、性能卓越的原生实现的任务,直接调用二进制文件通常是更可靠的选择。
要点
- 避免捆绑的 pdfjs‑dist 冲突,方法是不要加载多个嵌入不同版本的包。
- 利用操作系统层面的工具(
pdftoppm、tesseract)进行 PDF 栅格化和 OCR。 - 保持 Node.js 层简洁:只需少量
execSync调用和最小代码,即可取代笨重且易冲突的 npm 包装器。 - 简化 Docker 镜像,直接安装所需的二进制文件,而不是引入庞大且脆弱的 npm 包装器。