npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

receipt-ocr

v1.0.5

Published

A TypeScript library for extracting structured product data from receipt images using multimodal LLMs

Readme

ReceiptOCR

一个可复用的 TypeScript 库,用于借助多模态大语言模型从购物小票图片中提取结构化商品数据。

特性

  • 🚀 函数式 API:无状态、异步、可组合
  • 🎯 类型安全:完整的 TypeScript 类型定义
  • 🔌 依赖注入:验证逻辑由调用方提供
  • 📦 双模块支持:同时支持 ESM 和 CommonJS
  • 🤖 Gemini 驱动:使用 Google Gemini 多模态模型

安装

npm install receipt-ocr
# 或
pnpm add receipt-ocr

环境配置

在使用前,需要设置环境变量:

# Gemini API(必需)
export GEMINI_API_KEY=your-gemini-api-key

# 可选(默认:gemini-2.0-flash)
export GEMINI_MODEL=gemini-2.0-flash

# ppocr API(使用 OCR + LLM 模式时需要)
export PPOCR_API_URL=https://your-ppocr-api.com/ocr
export PPOCR_TOKEN=your-ppocr-token

或创建 .env 文件:

GEMINI_API_KEY="your-gemini-api-key"
PPOCR_API_URL="https://your-ppocr-api.com/ocr"
PPOCR_TOKEN="your-ppocr-token"

基础用法

import { extractReceiptItems } from 'receipt-ocr';
import fs from 'fs';

// 从文件读取图片
const imageBuffer = fs.readFileSync('receipt.jpg');

// 提取商品信息和总金额(默认启用自动验证)
const receipt = await extractReceiptItems(imageBuffer);

console.log(receipt);
// {
//   items: [
//     {
//       name: "有机牛奶 1L",
//       price: 12.5,
//       quantity: 1,
//       hasTax: false
//     },
//     {
//       name: "可口可乐瓶装",
//       price: 3.5,
//       quantity: 2,
//       hasTax: true,
//       taxAmount: 0.35,
//       deposit: 0.5,      // 押金已自动合并
//       discount: -0.5     // 折扣已自动合并
//     },
//     ...
//   ],
//   total: 95.75
// }

数据结构

小票数据

interface ReceiptData {
  items: ReceiptItem[];          // 商品列表
  total: number;                 // 小票总金额
  subtotal?: number;             // 小计(税前金额,如果小票上有)
  tax?: number;                  // 总税额(如果小票上有)
  totalDiscount?: number;        // 整单折扣(应用到整个账单的折扣,负数)
}

商品数据

每个商品包含以下字段:

interface ReceiptItem {
  name: string;                  // 商品名称
  price: number;                 // 单价
  quantity: number;              // 数量(默认 1)
  hasTax: boolean;               // 是否含税
  taxAmount?: number;            // 税额(可选)
  deposit?: number;              // 押金(可选,自动合并)
  discount?: number;             // 折扣(可选,自动合并)
}

附加费用自动合并

库会自动识别并合并押金(Deposit)和折扣(TPD)到对应的商品中,而不是作为独立的商品项返回:

  • 押金(deposit):如 "Deposit VL",会被合并到对应的瓶装商品中
  • 折扣(discount):如 "TPD",会被合并到对应的商品中(通常为负数)

这意味着您不需要手动处理这些附加费用,它们会自动关联到正确的商品上。

可选的金额字段

除了必需的 itemstotal 字段,ReceiptData 还包含以下可选字段:

  • subtotal(小计):如果小票上显示了税前总额,会自动提取此字段
  • tax(总税额):如果小票上显示了总税额(如 "TAX: $0.80"),会自动提取此字段
  • totalDiscount(整单折扣):如果有应用到整个账单的折扣(如会员折扣、优惠券),会提取为负数

重要区分

  • ReceiptItem.discount - 单个商品的折扣(如商品促销)
  • ReceiptData.totalDiscount - 整单折扣(如会员优惠、满减、优惠券)

示例:

{
  items: [...],
  total: 47.30,
  subtotal: 48.50,      // 税前总额
  tax: 0.80,            // 总税额
  totalDiscount: -2.00  // 会员折扣
}

提取模式

库支持两种提取模式,可通过 mode 参数选择:

多模态模式(默认)

// 方式 1: 不传 mode 参数(默认)
const receipt = await extractReceiptItems(imageBuffer);

// 方式 2: 显式指定
const receipt = await extractReceiptItems(imageBuffer, {
  mode: 'multimodal'
});

工作原理:Gemini 直接分析图片,一步提取结构化数据
优势:精度高,适合复杂布局
成本:图片占用 2000-6000 tokens

OCR + LLM 模式

const receipt = await extractReceiptItems(imageBuffer, {
  mode: 'ocr-llm',  // 指定 OCR + LLM 模式
  ocrConfig: {      // 必需:OCR API 配置
    apiUrl: process.env.PPOCR_API_URL,
    token: process.env.PPOCR_TOKEN,
    fileType: 1,  // 1=图片, 0=PDF
    // 可选参数
    useDocOrientationClassify: false,
    useDocUnwarping: false,
    useTextlineOrientation: false,
  }
});

工作原理

  1. ppocr 提取文本(800-1000 tokens)
  2. Gemini 解析文本(不用图片)

优势:成本低(节省 60-80%)
适用场景:大批量处理、成本敏感

模式选择建议

| 场景 | 推荐模式 | 原因 | |------|---------|------| | 首次使用 | multimodal | 默认模式,精度最高 | | 高精度要求 | multimodal | 复杂布局识别更准确 | | 大批量处理 | ocr-llm | 节省 60-80% 成本 | | 成本敏感 | ocr-llm | 文本占用 tokens 更少 |

高级用法

1. 自动验证(默认启用)

库默认使用 Google Search grounding 自动批量验证不确定的商品名称:

import { extractReceiptItems } from 'receipt-ocr';

// 默认启用自动验证
const receipt = await extractReceiptItems(imageBuffer);

// 如需禁用自动验证,显式设置为 false
const receiptWithoutVerify = await extractReceiptItems(imageBuffer, {
  autoVerify: false, // 禁用自动验证
});

console.log(receipt.items);  // 商品列表
console.log(receipt.total);  // 总金额

// 库会自动验证并补全模糊的商品名称
// 如果验证失败,会保持原始名称

优势

  • ✅ 批量处理,只需 1 次额外 API 调用
  • ✅ 使用 Google Search,覆盖面广
  • ✅ 自动处理,无需额外代码
  • ✅ 验证失败时自动保持原始数据

详细文档:自动验证功能

2. 自定义验证回调

当需要连接特定产品库时,可以使用自定义验证回调:

import { extractReceiptItems } from 'receipt-ocr';

const receipt = await extractReceiptItems(imageBuffer, {
  verifyCallback: async (name, context) => {
    // 调用外部搜索服务验证/补全商品名称
    const result = await myProductDatabase.search(name);
    
    if (result) {
      return { verifiedName: result.fullName };
    }
    
    // 返回 null 保持原样
    return null;
  }
});

3. 组合使用

两种验证方式可以同时使用(自动验证默认启用):

const receipt = await extractReceiptItems(imageBuffer, {
  // autoVerify 默认为 true,会先用 Google Search 批量验证
  verifyCallback: async (name, context) => {
    // 如果自动验证失败,再用自定义逻辑
    const result = await myProductDatabase.search(name);
    return result ? { verifiedName: result.name } : null;
  },
});

验证回调接口

type VerificationCallback = (
  name: string,
  context: {
    rawText: string;           // OCR 原始文本
    allItems: ReceiptItem[];   // 所有已解析商品(不含 total)
  }
) => Promise<{ verifiedName: string } | null>;

访问总金额

const receipt = await extractReceiptItems(imageBuffer);

// 访问商品列表
receipt.items.forEach(item => {
  console.log(`${item.name}: ¥${item.price} × ${item.quantity}`);
});

// 访问总金额
console.log(`总计: ¥${receipt.total}`);

图片输入格式

支持以下三种格式:

// 1. Buffer
const buffer = fs.readFileSync('receipt.jpg');
await extractReceiptItems(buffer);

// 2. Base64 字符串
const base64 = 'iVBORw0KGgoAAAANSUhEUgAA...';
await extractReceiptItems(base64);

// 3. 图片 URL
const url = 'https://example.com/receipt.jpg';
await extractReceiptItems(url);

注意事项

  • 图片大小限制:单次请求(包括图片和提示文本)总大小不能超过 20MB
  • URL 处理方式:URL 图片会被自动下载并转换为 base64 后发送给 API
  • 性能建议:对于购物小票等文档图片,通常大小在几百 KB 到几 MB 之间,完全在限制范围内

API 参考

extractReceiptItems()

function extractReceiptItems(
  image: ImageInput,
  options?: ExtractOptions
): Promise<ReceiptData>

参数

  • image: ImageInput - 图片输入,支持 Buffer、Base64 字符串或图片 URL
  • options?: ExtractOptions - 可选配置对象

ExtractOptions 接口

interface ExtractOptions {
  // 提取模式(新增)
  mode?: 'multimodal' | 'ocr-llm';  // 默认:'multimodal'
  
  // OCR 配置(mode='ocr-llm' 时必需)
  ocrConfig?: {
    apiUrl: string;                      // ppocr API 地址
    token: string;                       // API token
    fileType?: 0 | 1;                    // 0=PDF, 1=图片(默认:1)
    useDocOrientationClassify?: boolean; // 文档方向分类(默认:false)
    useDocUnwarping?: boolean;           // 文档矫正(默认:false)
    useTextlineOrientation?: boolean;    // 文本行方向(默认:false)
  };
  
  // 验证相关
  autoVerify?: boolean;                  // 自动验证(默认:true)
  verifyCallback?: VerificationCallback; // 自定义验证回调
}

返回值

interface ReceiptData {
  items: ReceiptItem[];       // 商品列表
  total: number;              // 总金额(必需)
  subtotal?: number;          // 小计/税前金额(可选)
  tax?: number;               // 总税额(可选)
  totalDiscount?: number;     // 整单折扣(可选,负数)
}

interface ReceiptItem {
  name: string;          // 商品名称
  price: number;         // 单价
  quantity: number;      // 数量
  hasTax: boolean;       // 是否含税
  taxAmount?: number;    // 该商品的税额(可选)
  deposit?: number;      // 押金(可选)
  discount?: number;     // 该商品的折扣(可选,负数)
}

使用示例

// 默认模式(multimodal)
const receipt = await extractReceiptItems(imageBuffer);

// OCR + LLM 模式
const receipt = await extractReceiptItems(imageBuffer, {
  mode: 'ocr-llm',
  ocrConfig: {
    apiUrl: process.env.PPOCR_API_URL,
    token: process.env.PPOCR_TOKEN,
    fileType: 1
  }
});

// 带自定义验证
const receipt = await extractReceiptItems(imageBuffer, {
  mode: 'ocr-llm',
  ocrConfig: { ... },
  verifyCallback: async (name, context) => {
    const result = await searchDatabase(name);
    return result ? { verifiedName: result } : null;
  }
});

策略接口(供扩展)

库预留了完整的策略接口,方便未来扩展:

import { VerificationStrategy } from 'receipt-ocr';

const myStrategy: VerificationStrategy = {
  verify: async (name, context) => {
    const verified = await searchProductDB(name);
    return { verifiedName: verified };
  }
};

开发

# 安装依赖
npm install

# 类型检查
npm run type-check

# 构建
npm run build

# 开发模式(监听变化)
npm run dev

设计原则

  1. 无状态:每次调用独立,无副作用
  2. 确定性:不猜测不确定的数据,通过验证机制确保准确性
  3. 可组合性:验证逻辑通过依赖注入提供
  4. 正确性优先:内部处理不确定性,对外只返回可靠数据

License

MIT