当两个 npm 包争夺 pdfjs-dist 时:转向系统二进制文件

发布: (2026年3月17日 GMT+8 03:13)
5 分钟阅读
原文: Dev.to

Source: Dev.to

请提供您希望翻译的具体文本内容,我将为您翻译成简体中文。

问题

我正在为一个 Next.js 应用添加对扫描 PDF 的 OCR 支持。计划很简单:

  1. 使用 pdf-to-img 将 PDF 页面栅格化。
  2. 将图像管道传输给 Tesseract
  3. 连接提取的文本。

在安装 pdf-to-img 并部署到预生产环境后,上传扫描的 PDF 时出现以下错误:

Error: API version does not match Worker version

为什么会发生

项目已经使用 unpdf 从数字 PDF 中提取文本。pdf-to-imgunpdf 都捆绑了各自的 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-utilspdftoppm)和 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)进行包装时:

  1. 检查维护情况 – 包装器是否得到良好维护,还是仅仅是一个薄层包装?
  2. 留意传递冲突 – 包装器是否捆绑了自己的库副本,可能与其他依赖产生冲突?
  3. 考虑 Docker 简洁性 – 直接安装二进制文件通常能得到更简洁的 Dockerfile。

npm 生态系统在纯 JavaScript 问题上表现出色。对于那些已有长期、性能卓越的原生实现的任务,直接调用二进制文件通常是更可靠的选择。

要点

  • 避免捆绑的 pdfjs‑dist 冲突,方法是不要加载多个嵌入不同版本的包。
  • 利用操作系统层面的工具pdftoppmtesseract)进行 PDF 栅格化和 OCR。
  • 保持 Node.js 层简洁:只需少量 execSync 调用和最小代码,即可取代笨重且易冲突的 npm 包装器。
  • 简化 Docker 镜像,直接安装所需的二进制文件,而不是引入庞大且脆弱的 npm 包装器。
0 浏览
Back to Blog

相关文章

阅读更多 »