纯前端批量识别发票二维码:PDF.js + jsQR 实战
技术家族 2026-05-23 lkb 40 15 分钟

纯前端批量识别发票二维码:PDF.js + jsQR 实战

不上传文件,直接在浏览器里批量提取电子发票信息

记录一次纯前端电子发票识别工具的实现过程,核心是 PDF.js、jsQR 和浏览器端 OCR 的组合。

分享到:

最近在做财务报销相关的需求,需要从大量电子发票 PDF 中提取发票号码、购买方、销售方、金额等信息。手动复制粘贴效率极低,于是研究了一下能不能纯前端实现自动识别。

折腾了几天,最终用 PDF.js + jsQR + Tesseract.js 实现了一个完全在浏览器端运行的发票批量识别工具,不需要后端,不上传文件,隐私安全。本文记录一下核心实现思路。


技术选型
      

功能 说明
PDF 解析 pdf.js Mozilla 出品,浏览器端解析 PDF
二维码识别 jsQR 纯 JS 二维码解码
图片 OCR Tesseract.js Google Tesseract 的 WebAssembly 版本

核心流程

1. PDF 文字层提取

电子发票 PDF 本身带有文字层,pdf.js 可以直接提取,不需要 OCR,准确率高:

async function extractPdfText(pdf) {
    let fullText = '';
    for (let i = 1; i <= pdf.numPages; i++) {
        const page = await pdf.getPage(i);
        const textContent = await page.getTextContent();
        // items.str 就是每个文字块的内容
        const pageText = textContent.items.map(item => item.str).join(' ');
        fullText += pageText + '\n';
    }
    return fullText;
}

2. 扫描发票二维码

把 PDF 页面渲染到离屏 canvas,再用 jsQR 扫描像素数据:

async function scanPageForQR(pdf, pageNum, scale = 2.0) {
    const page = await pdf.getPage(pageNum);
    const viewport = page.getViewport({ scale });
    const canvas = document.createElement('canvas');
    canvas.width = viewport.width;
    canvas.height = viewport.height;
    const ctx = canvas.getContext('2d');
    await page.render({ canvasContext: ctx, viewport }).promise;

    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const code = jsQR(imageData.data, imageData.width, imageData.height, {
        inversionAttempts: 'dontInvert'
    });
    return code ? code.data : null;
}

注意 scale 建议设 2.0 以上,分辨率太低二维码识别率会下降。

3. 解析二维码内容

增值税发票二维码格式是逗号分隔的字符串:

01,32,,26512000001023417645,38.69,20260317,,7147


字段顺序:版本, 类型码, 发票代码, 发票号码, 金额, 日期, 校验码

function parseInvoiceQR(qrText) {
    const parts = qrText.split(',');
    const invoiceTypeMap = {
        '01': '增值税专用发票',
        '04': '增值税普通发票',
        '32': '电子发票(普通发票)',
        // ...
    };
    const typeCode = parts[1].trim();
    return {
        invoiceType: invoiceTypeMap[typeCode] || typeCode,
        invoiceNumber: parts[3].trim(),
        invoiceDate: formatDate(parts[5].trim()), // 注意:parts[4]=金额,parts[5]=日期
        amount: parts[4].trim(),
    };
}

踩坑:不同版本发票二维码字段顺序不同,新版电子发票 parts[4] 是金额,parts[5] 才是日期,不要搞反。

4. 从 PDF 文本中提取结构化字段

这是最麻烦的部分。不同发票 PDF 的文本提取顺序差异很大:

  • 新版电子发票:标签和值分离,"名称:"标签在前半段,公司名在后半段
  • 旧版专票:标签值紧邻,名称: XXX科技有限公司
  • 部分发票:销售方在购买方前面出现(布局不同)

针对这些差异,我的处理策略:

// 优先匹配"名称: XXX公司"紧邻格式
const nameInline = [...t.matchAll(
    /名称[::]\s*([\u4e00-\u9fa5()()][^\d::\n]{2,40}?(?:有限公司|股份公司|分公司|个体工商户|餐厅))/g
)];

// 通过"购买方"/"销售方"标签位置判断顺序
const buyerLabelPos = t.search(/购\s*买\s*方/);
const sellerLabelPos = t.search(/销\s*售\s*方/);
const sellerFirst = buyerLabelPos > sellerLabelPos; // 旧版专票销售方在前

if (nameInline.length >= 2) {
    result.buyerName = sellerFirst ? nameInline[1][1] : nameInline[0][1];
    result.sellerName = sellerFirst ? nameInline[0][1] : nameInline[1][1];
}

税号同理,通过标签位置判断哪个是购买方哪个是销售方:

const taxRe = /\b([0-9A-Z]{15,20})\b/g;
const taxes = [...t.matchAll(taxRe)];
if (buyerPos > sellerPos) {
    // 销售方先出现
    result.sellerTaxId = taxes[0][1];
    result.buyerTaxId = taxes[1][1];
} else {
    result.buyerTaxId = taxes[0][1];
    result.sellerTaxId = taxes[1][1];
}

5. 图片发票走 OCR

图片发票没有文字层,用 Tesseract.js 识别:

const worker = await Tesseract.createWorker('chi_sim+eng', 1, {});
const { data: { text } } = await worker.recognize(canvas);
await worker.terminate();

OCR 识别出来的文本每个字之间有空格,所以正则要先 text.replace(/\s+/g, '') 去掉所有空白再匹配。

批量处理

核心是维护一个文件队列,逐一处理:

async function startBatchScan() {
    const pending = fileQueue.filter(q => q.status === 'pending');
    for (let i = 0; i < pending.length; i++) {
        const item = pending[i];
        item.status = 'processing';
        try {
            const data = await processFile(item.file, item.type);
            item.status = 'done';
            results.push({ fileName: item.file.name, data });
        } catch (e) {
            item.status = 'error';
            results.push({ fileName: item.file.name, error: e.message });
        }
        renderResultTable(); // 每识别一张就更新表格
    }
}

效果

实测下来,新版电子发票 PDF 识别准确率很高,发票号码、金额、购买方/销售方基本都能正确提取。图片识别因为 OCR 精度问题,税号偶尔会有字符识别错误。

识别结果支持导出 CSV,带 BOM 头,Excel 直接打开中文不乱码:

const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8' });

落地时最有用的部分

整个方案完全在浏览器端运行,核心依赖:

  • pdf.js 解析 PDF 文字层和渲染页面
  • jsQR 扫描二维码
  • Tesseract.js 处理图片 OCR

最麻烦的是不同发票格式的兼容,需要针对各种布局写不同的正则策略。如果你也有类似需求,可以参考这个思路,或者直接用我做好的在线工具试试效果:发票批量识别工具


如有问题欢迎评论区交流。

文章标签

下一篇

没有了

相关文章

评论 (3)

发表评论

审核通过后公开显示
文章评论测试 已审核 2026-05-24 18:39

文章评论测试677c48eb-3647-413b-8519-375ff7bb8a68

1
100638 已审核 2026-05-24 18:41

qwereeeeeadfadsf

审核修复测试 已审核 2026-05-24 18:45

审核修复测试08856f06-4a94-49d2-89ad-2b72c4bfaebb