如何实现可同时在Web Worker和Node.js环境中运行的扫描版PDF文本提取方案
如何实现可同时在Web Worker和Node.js环境中运行的扫描版PDF文本提取方案
嘿,这个问题我之前做PDF处理的时候也踩过坑!既要避开主线程卡顿,又要同时兼容Web Worker和Node.js,核心就是得把依赖DOM的部分给替换掉,咱们一步步来解决:
核心思路:解耦DOM依赖,用跨环境的图像数据处理方式
你原来的代码卡在document.createElement('canvas')——这玩意儿在Web Worker和Node.js里都没法用。所以我们要把PDF页面渲染成像素数据(ImageData)或者Buffer,而不是死磕浏览器DOM里的Canvas元素,同时让Tesseract.js直接处理这些原生数据格式。
1. Web Worker环境下的适配方案
Web Worker里支持OffscreenCanvas,这是不依赖DOM的独立画布对象,刚好能和PDF.js的渲染逻辑配合,而且Tesseract.js也能直接识别它的图像数据。
下面是修改后的Worker代码:
import * as pdfjsLib from 'pdfjs-dist'; import Tesseract from 'tesseract.js'; // 配置PDF.js的Worker路径 pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs-dist/build/pdf.worker.js'; async function extractTextFromScannedPDF(arrayBuffer) { const pdf = await pdfjsLib.getDocument(arrayBuffer).promise; let fullText = ''; // 遍历所有页面(可以改成只处理指定页面) for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) { const page = await pdf.getPage(pageNum); // 放大页面提升OCR准确率,可根据实际情况调整 const viewport = page.getViewport({ scale: 2.0 }); // 使用OffscreenCanvas替代DOM Canvas const canvas = new OffscreenCanvas(viewport.width, viewport.height); const context = canvas.getContext('2d'); await page.render({ canvasContext: context, viewport }).promise; // 获取图像数据传给Tesseract const imageData = context.getImageData(0, 0, viewport.width, viewport.height); const { data: { text } } = await Tesseract.recognize( imageData, 'eng', { logger: m => console.log(`Page ${pageNum}: ${m.status}`) } ); fullText += `--- Page ${pageNum} --- ${text} `; } return fullText; } // 监听主线程传来的PDF数据 self.onmessage = async (e) => { try { const text = await extractTextFromScannedPDF(e.data.arrayBuffer); self.postMessage({ success: true, text }); } catch (err) { self.postMessage({ success: false, error: err.message }); } };
主线程调用Worker的代码示例:
function runExtractionInWorker(file) { const worker = new Worker('./pdf-ocr-worker.js'); const fileReader = new FileReader(); fileReader.readAsArrayBuffer(file); fileReader.onload = (e) => { worker.postMessage({ arrayBuffer: e.target.result }); worker.onmessage = (msg) => { if (msg.data.success) { console.log('提取完成:', msg.data.text); // 在这里处理提取后的文本 } else { console.error('提取失败:', msg.data.error); } worker.terminate(); }; }; } // 绑定文件选择事件 document.getElementById('pdf-input').addEventListener('change', (e) => { runExtractionInWorker(e.target.files[0]); });
2. Node.js环境下的适配方案
Node.js里没有Canvas和DOM,所以需要用pdfjs-dist的Node版本,搭配canvas模块替代浏览器Canvas,同时Tesseract.js在Node环境中可以直接处理Canvas实例或者Buffer。
首先安装依赖:
npm install pdfjs-dist canvas tesseract.js
Node.js代码示例:
const fs = require('fs').promises; const pdfjsLib = require('pdfjs-dist'); const { createCanvas } = require('canvas'); const Tesseract = require('tesseract.js'); // Node环境下配置PDF.js的Worker路径 pdfjsLib.GlobalWorkerOptions.workerSrc = require.resolve('pdfjs-dist/build/pdf.worker.js'); async function extractTextFromScannedPDF(filePath) { const arrayBuffer = await fs.readFile(filePath); const pdf = await pdfjsLib.getDocument(arrayBuffer).promise; let fullText = ''; for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) { const page = await pdf.getPage(pageNum); const viewport = page.getViewport({ scale: 2.0 }); // 使用node-canvas创建画布 const canvas = createCanvas(viewport.width, viewport.height); const context = canvas.getContext('2d'); await page.render({ canvasContext: context, viewport }).promise; // Node环境下Tesseract可直接识别Canvas实例 const { data: { text } } = await Tesseract.recognize( canvas, 'eng', { logger: m => console.log(`Page ${pageNum}: ${m.status}`) } ); fullText += `--- Page ${pageNum} --- ${text} `; } return fullText; } // 调用示例 extractTextFromScannedPDF('./your_pdf.pdf') .then(text => { console.log('提取结果:', text); return fs.writeFile('./output.txt', text); }) .catch(err => console.error('出错:', err));
关键改动说明
- 移除DOM依赖:Web Worker用
OffscreenCanvas,Node用node-canvas,彻底摆脱对document对象的依赖 - 统一OCR逻辑:Tesseract.js支持多种输入类型(ImageData、Canvas、Buffer),跨环境可以复用核心识别代码
- 性能优化:Web Worker把重计算移到后台线程,Node里用异步遍历避免阻塞事件循环,都能解决大PDF卡顿的问题
额外小建议
- 调整
scale参数:放大页面(比如设为2.0)能显著提升低分辨率扫描件的OCR准确率 - 批量处理:如果PDF页数极多,可以分批次处理,避免内存占用过高
- 多语言支持:需要识别其他语言时,把Tesseract的语言代码换成对应值(比如
'chi_sim'对应简体中文),并确保下载对应的训练包
备注:内容来源于stack exchange,提问作者Devr
相关产品推荐
相关产品推荐

