Распознать чек со скриншота нейросетью: пример на 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 стоимость картинки зависит от её размера в пикселях, а большие изображения всё равно уменьшаются на стороне модели.
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:
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 с подписанной ссылкой, внутренний файловый сервер), куда внешний сервис не достучится.
Проверка извлечённых данных
Модель может ошибиться в цифре или прочитать скидку как товар. Проверяем формат и сверяем сумму позиций с итогом:
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 показывайте пользователю для подтверждения, а не сохраняйте молча.
Сохранение в базу
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 или страница «Модели» в кабинете.
Персональные данные
На чеке могут быть имя кассира, часть номера карты, адрес магазина, иногда ФИО покупателя. Рекомендации:
- Не храните исходные изображения дольше, чем нужно, и не пишите их в логи.
- Сохраняйте только нужные поля (дата, сумма, позиции).
- Если данные нельзя передавать внешнему сервису по вашим правилам, используйте локальный 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.
Похожие статьи
- Промпт-инъекция: что это и как защитить бота и агентаОбъясняем, как работает промпт-инъекция через пользовательский ввод и чужие документы, и какие меры снижают ущерб: изоляция данных, права инструментов, лимиты ключа.
- Свой прокси к нейросети на Node.js: не показывать API-ключ во фронтендеНебольшой сервер на Express между вашим фронтендом и API нейросети: ключ остаётся на сервере, а модели, длина запроса и частота ограничены.
- DeepSeek API на Python: запросы, стриминг, JSON и обработка ошибокГотовый код для DeepSeek V4.1 Flash и V4 Pro на OpenAI SDK: запрос, стриминг, разбор JSON и аккуратная обработка ошибок.
- Промпт-инжиниринг: примеры и 12 готовых шаблонов для работыДвенадцать шаблонов промптов для рабочих задач: что в них написано, почему именно так и сколько стоит один вызов по API.