Распознать чек со скриншота нейросетью: пример на Python

Пользователь фотографирует чек, модель читает дату, сумму и позиции. Показываем, как отправить изображение в запросе и получить структурированные данные.

4 мин · · разработка · бизнес

Распознавание чеков — частая задача в учёте расходов, бухгалтерских сервисах и приложениях для авансовых отчётов. Пользователь фотографирует чек или присылает скриншот из банковского приложения, а система должна достать дату, сумму и позиции. Ниже — как отправить изображение модели через API в OpenAI-формате, получить JSON и проверить его перед сохранением.

Почему нейросеть, а не только OCR

Классический OCR (например, Tesseract) возвращает текст, но не понимает структуру: какая строка — итог, какая — скидка, где название магазина. Модель с поддержкой изображений читает чек целиком и сразу возвращает поля в нужном формате: видит строку «Итого» и понимает, что число рядом — сумма.

Плюсы:

  • Сразу структурированные данные (JSON), без отдельного парсера.
  • Работает с разными форматами: кассовые чеки, скриншоты заказов, PDF-чеки, сохранённые как картинка.
  • Правила извлечения меняются правкой промпта, а не кода.

Минусы:

  • Каждый запрос стоит денег, а локальный OCR бесплатен.
  • Модель может ошибиться в цифре, поэтому нужна проверка (ниже).
  • Изображение уходит внешнему сервису — учитывайте это для персональных данных.

Отправка изображения в OpenAI-формате

В OpenAI-совместимом API картинка передаётся как часть содержимого сообщения: элемент {"type": "image_url", "image_url": {"url": ...}}, где url — data URL вида data:image/jpeg;base64,<данные>. Рядом идёт текстовая часть с инструкцией.

Перед отправкой уменьшаем фото: у моделей Claude стоимость картинки зависит от её размера в пикселях, а большие изображения всё равно уменьшаются на стороне модели.

bash
import base64
import io
import json
from openai import OpenAI
from PIL import Image

client = OpenAI(
    base_url="https://api.zukko.pro/v1",
    api_key="fetch_ваш_ключ",
)

SYSTEM_PROMPT = """Ты извлекаешь данные из изображений чеков.
Верни ТОЛЬКО JSON без markdown:
{

Если у вас уже есть байты PNG или WebP и уменьшать их не нужно, достаточно поменять тип в data URL: data:image/png;base64,....

Если чек лежит по ссылке

Формат OpenAI допускает и обычную https-ссылку в image_url.url, но тогда картинку должен скачать уже провайдер модели: ссылка должна быть публичной и доступной извне. Надёжнее скачать файл самим и отправить тот же data URL:

bash
import tempfile
import requests

def recognize_receipt_from_url(image_url: str) -> dict:
    resp = requests.get(image_url, timeout=30)
    resp.raise_for_status()
    with tempfile.NamedTemporaryFile(suffix=".img") as tmp:
        tmp.write(resp.content)
        tmp.flush()
        return recognize_receipt(prepare_image(tmp.name))

Так работает и с приватными хранилищами (S3 с подписанной ссылкой, внутренний файловый сервер), куда внешний сервис не достучится.

Проверка извлечённых данных

Модель может ошибиться в цифре или прочитать скидку как товар. Проверяем формат и сверяем сумму позиций с итогом:

bash
from datetime import date

def validate_receipt(data: dict, tolerance: float = 1.0) -> dict:
    """Проверяет поля; при расхождениях чек уходит на ручную проверку"""
    errors = []

    if data.get("date"):
        try:
            date.fromisoformat(data["date"])
        except (ValueError, TypeError):
            errors.append(f"неверная дата: {data['date']}")

    total = data.get("total")
    if total is not None and (not isinstance(total, (int, float)) or total < 0):

Чеки с needs_review показывайте пользователю для подтверждения, а не сохраняйте молча.

Сохранение в базу

bash
import sqlite3
from datetime import datetime

def save_receipt(data: dict, db_path: str = "receipts.db") -> int | None:
    check = validate_receipt(data)
    if not check["valid"]:
        print("Чек не сохранён:", check["errors"])
        return None
    conn = sqlite3.connect(db_path)
    conn.execute("""CREATE TABLE IF NOT EXISTS receipts (
        id INTEGER PRIMARY KEY, date TEXT, total REAL, currency TEXT,
        vendor TEXT, confidence REAL, created_at TEXT)""")
    conn.execute("""CREATE TABLE IF NOT EXISTS receipt_items (
        id INTEGER PRIMARY KEY, receipt_id INTEGER REFERENCES receipts(id),

Размер изображений

  • По документации Anthropic, у моделей Claude картинка стоит примерно ⌈ширина/28⌉ × ⌈высота/28⌉ токенов, а у новых моделей длинная сторона до 2576 px и до ~4800 токенов на изображение. Фото 1176×1568 после prepare_image — около 2350 токенов.
  • Для чеков обычно хватает 1200–1600 px по длинной стороне: текст читается, а запрос дешевле.
  • Слишком большой запрос шлюз отклонит с ошибкой 413 — уменьшайте и сжимайте фото до отправки.

Выбор модели и стоимость

В примере используется claude-sonnet-5 ($0,30/$0,30 за 1M токенов); изображения принимают и другие модели Claude, например claude-opus-5-5 по той же цене. Поддерживает ли картинки конкретная модель из каталога, проверьте коротким тестовым запросом с изображением. Какая модель точнее на ваших чеках, покажет сравнение на 30–50 реальных фото с эталонными значениями.

Расчёт на 1000 чеков в месяц: ~2400 токенов на картинку + ~300 на промпт, ~300 выходных токенов (JSON):

(1000 × 2700 × 0.30 / 1M) + (1000 × 300 × 0.30 / 1M) = $0.81 + $0.09 = $0.90 в месяц

Если отправлять фото с телефона без уменьшения, картинка может стоить до ~4800 токенов: (1000 × 5100 × 0.30 / 1M) + $0.09 = $1.62 в месяц.

Актуальный список моделей: GET https://api.zukko.pro/v1/models или страница «Модели» в кабинете.

Персональные данные

На чеке могут быть имя кассира, часть номера карты, адрес магазина, иногда ФИО покупателя. Рекомендации:

  1. Не храните исходные изображения дольше, чем нужно, и не пишите их в логи.
  2. Сохраняйте только нужные поля (дата, сумма, позиции).
  3. Если данные нельзя передавать внешнему сервису по вашим правилам, используйте локальный OCR.

Настройка ключа

Создайте ключ в кабинете Zukko, ограничьте его нужной моделью и поставьте дневной лимит. Для 30 чеков в день (около 900 в месяц) по расчёту выше хватит $1 в месяц. Формат запроса описан в справке по Chat Completions, базовая настройка — в примерах на Python.

Частые вопросы

Можно ли взять модель дешевле?

Можно попробовать, если модель принимает изображения: отправьте тестовый запрос с картинкой и прогоните ту же выборку из 30–50 чеков. Сравнивайте не только цену, но и долю чеков, ушедших на ручную проверку.

Что если на чеке рукописный текст?

Модель попытается его прочитать, но результат менее предсказуем. Такие чеки почти всегда стоит отправлять пользователю на подтверждение.

Как обработать много чеков?

Отправляйте запросы параллельно небольшими группами и обрабатывайте ошибку 429 повтором с паузой. Дневной лимит ключа защитит бюджет, если что-то пойдёт не так.

Нужно ли дообучать модель на чеках конкретного магазина?

Нет. Если какой-то формат распознаётся плохо, добавьте в системный промпт описание этого формата или пример ответа, а также проверьте качество фото.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.