Сбои OCR API делятся примерно на три класса: запрос вообще не принят (401, 413, 415), запрос ограничен по частоте или превысил тайм-аут (429, 5xx), запрос прошёл успешно, но результат непригоден (пустой текст, «мусорные» символы, низкая уверенность). Первые два класса определяются по коду состояния; третий чаще всего ошибочно списывают на «сломанный интерфейс».

Ниже — порядок диагностики по симптомам, подготовка изображений, стратегия повторов и поля журнала, а в конце — готовый к повторному использованию код обработки ошибок.

Примечание: интерфейсы OCR, коды ошибок и правила лимитов у разных поставщиков заметно различаются. Материал описывает типовое поведение на октябрь 2026 года; ориентируйтесь на документацию провайдера и фактические ответы. Названия OpenAI, Google, Microsoft, Alibaba Cloud, Tencent Cloud, Baidu AI Cloud, Tesseract и PaddleOCR принадлежат их правообладателям и приведены только для технического пояснения.

1. Сначала сохраните четыре вида доказательств

Не спешите повторять запрос. Сначала зафиксируйте:

  • HTTP-статус и поля error.code и error.message в теле ответа;
  • идентификатор запроса или x-request-id в заголовках;
  • формат исходного файла, размер, разрешение в пикселях и ориентацию EXIF;
  • время вызова, адрес интерфейса и имя модели.
Код Типичное значение Что проверить первым
400 Некорректные параметры или изображение Base64, URL, имена полей, целостность файла
401 Ошибка аутентификации Наличие ключа, срок действия, лишние пробелы
403 Нет прав или лимит аккаунта Права на модель, статус проекта, баланс
413 Слишком большое тело запроса или файл Размер, разрешение, число страниц PDF
415 Неподдерживаемый тип содержимого Content-Type, расширение, реальная кодировка
429 Лимит частоты или параллелизма QPS, число параллельных запросов, Retry-After
500/502/503/504 Сбой или тайм-аут вышестоящего сервиса Повтор, деградация, страница статуса

2. 401 и 403: сначала личность, потом права

401 означает, что сервер не узнаёт ключ. Частые причины: в заголовке нет Authorization: Bearer, при копировании добавились пробел или перевод строки, переменная окружения пуста, ключ уже ротирован или ключ одной платформы отправлен на адрес другой.

Проверяйте минимальным запросом, а не в полном бизнес-процессе. Смотрите на значение, которое реально уходит в заголовке, а не только на конфигурационный файл. 403 обычно означает, что ключ действителен, но у него нет прав на эту модель или интерфейс, либо аккаунт ограничен по квоте, а проект приостановлен.

Отдельно учтите: ключи шлюза и нативные ключи провайдера обычно не взаимозаменяемы. При смене адреса сверяйте вместе ключ, имя модели и путь запроса.

3. 413 и 415: слишком большой файл и неверный формат

413 — это не «не удалось распознать содержимое», а отказ на транспортном уровне. Типичные причины:

  • одно изображение превышает лимит размера интерфейса;
  • слишком большое разрешение, после декодирования не хватает памяти;
  • в PDF слишком много страниц или за один раз отправлено несколько страниц;
  • после Base64 объём вырос примерно на треть, и файл, который едва проходил, перестал проходить.

415 чаще связан с форматом: интерфейс принимает только JPG и PNG, а вы отправили HEIC, TIFF или BMP; либо расширение JPG, но внутри WebP; либо Content-Type указан как application/json, тогда как интерфейсу нужен multipart/form-data.

Подготовку изображений рекомендуем вести в таком порядке:

  1. Прочитайте файл библиотекой изображений и определите реальный формат — расширению не доверяйте.
  2. Пропорционально уменьшите изображение по длинной стороне, например до 2000–3000 пикселей, и повторите тест.
  3. Исправьте ориентацию EXIF, чтобы текст не оказался повёрнутым и распознавание не вернуло пустоту.
  4. Приведите файл к поддерживаемому формату и подберите качество сжатия, не размывая символы.
  5. Многостраничный PDF разбирайте постранично или используйте интерфейс для целых документов.
  6. Записывайте размер и разрешение до и после обработки, чтобы понять, на каком шаге возникло превышение.

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

4. 429: лимиты, параллелизм и ритм повторов

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

Принципы обработки:

  • читайте Retry-After и, если он есть, ждите указанное время;
  • если его нет, используйте экспоненциальную задержку со случайным разбросом, чтобы клиенты не повторяли одновременно;
  • повторяйте только 429 и 5xx; повторы при 401, 413 и 415 бессмысленны;
  • задайте максимальное число повторов и общий тайм-аут, чтобы задачи не копились;
  • ставьте пиковый трафик в очередь и отправляйте его плавно, по возможностям сервера.

Повторять бесконечно — не значит надёжно. Для интерфейсов с оплатой за вызов уточните у провайдера, тарифицируются ли неудачные запросы: слепые повторы могут увеличить расходы.

5. Тайм-ауты и 5xx: сеть или вышестоящий сервис

Сначала разделите тайм-аут соединения и тайм-аут чтения. Тайм-аут соединения обычно указывает на сеть, DNS, прокси или межсетевой экран; тайм-аут чтения означает, что запрос дошёл, но обработка затянулась — это типично для больших изображений, многостраничных PDF и очередей на стороне провайдера.

Для OCR задайте отдельный тайм-аут, не наследуйте короткий тайм-аут текстовых интерфейсов. Синхронный вызов подходит для небольших изображений; для больших документов используйте асинхронные задачи с опросом статуса — это заметно снижает долю тайм-аутов. При 5xx повторите один-два раза, затем переключитесь на резервный вариант и запишите request ID для сверки с поставщиком.

6. Пустой результат: успешный запрос не равен пригодному результату

«Пустой результат» — самый частый случай неверной диагностики. Интерфейс возвращает 200, но текст пуст, содержит только знаки препинания или все поля равны null. Порядок проверки:

  • Есть ли текст на изображении: откройте оригинал глазами и убедитесь, что это не пустая страница и не чистая картинка.
  • Правильна ли ориентация: фотографии с телефона несут информацию о повороте, и сервер может не учесть EXIF.
  • Не слишком ли мелкий текст: мелкие символы на общем превью часто распознаются после увеличения и обрезки.
  • Достаточен ли контраст: светло-серый текст, тени, блики и просвечивание снижают полноту.
  • Совпадает ли язык: модель для китайского на русском или английском документе может выдать лишь отдельные символы.
  • Не выходит ли вёрстка за рамки: многоколоночный текст, вертикальная запись, таблицы и печати традиционный OCR часто пропускает.
  • Не отдаёт ли интерфейс только структурированные поля: иногда полный текст лежит в другом поле, а код читает лишь text.

Если изображение чёткое, ориентация верна и язык совпадает, а результат пуст, переходите к следующему уровню: измените параметры подготовки, смените модель, используйте мультимодальное извлечение или ручную проверку. Не надейтесь, что «ещё один вызов» решит проблему.

Низкую уверенность тоже нужно обрабатывать. Задайте порог для полей и отправляйте всё ниже порога в ручную очередь; для сумм, номеров документов и дат применяйте проверку формата и перекрёстную сверку. Журналируйте распределение уверенности — так видно, качество ухудшается постепенно или было плохим всегда.

7. Готовый код обработки ошибок

Пример предполагает, что интерфейс принимает multipart/form-data; реальные путь и имена полей сверьте с документацией провайдера.

import random
import time
import requests

FATAL = {400, 401, 403, 413, 415}
RETRYABLE = {429, 500, 502, 503, 504}

def ocr_call(image_bytes, api_key, endpoint, max_retries=5):
    last_error = None
    for attempt in range(max_retries):
        try:
            resp = requests.post(
                endpoint,
                headers={"Authorization": f"Bearer {api_key}"},
                files={"file": ("page.png", image_bytes, "image/png")},
                timeout=(5, 90),  # соединение 5 с, чтение 90 с
            )
        except requests.Timeout as exc:
            last_error = exc
        else:
            if resp.status_code in FATAL:
                raise RuntimeError(f"fatal_{resp.status_code}: {resp.text[:300]}")
            if resp.status_code == 200:
                data = resp.json()
                text = (data.get("text") or "").strip()
                if not text:
                    raise ValueError("empty_result: check rotation, crop and format")
                return data
            if resp.status_code not in RETRYABLE:
                raise RuntimeError(f"unexpected_{resp.status_code}")
            last_error = RuntimeError(resp.text[:300])

        retry_after = resp.headers.get("Retry-After") if "resp" in locals() else None
        wait = float(retry_after) if retry_after else min(2 ** attempt + random.random(), 30)
        time.sleep(wait)

    raise RuntimeError(f"retry_exhausted: {last_error}")

Код делает задержки только для повторяемых ошибок, немедленно падает на 401, 413 и 415, а «пустой результат» считает бизнес-аномалией, требующей вмешательства человека, а не сетевой ошибкой.

8. Что писать в журнал и мониторинг

Минимальный набор полей: request ID, статус, код ошибки, длительность, размеры изображения, размер файла, число повторов, количество распознанных символов, средняя уверенность, признак ручной проверки. Группируйте статистику по интерфейсу и модели — так видно, проблема в определённом типе изображений или в нестабильном вышестоящем сервисе.

Для мониторинга задайте три оповещения: резкий рост доли ошибок, резкий рост доли пустых результатов и резкий рост P95 задержки. Среднее время легко скрывает длинный хвост, а тайм-ауты OCR чаще всего возникают именно в хвосте.

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

401 не исчезает после смены ключа?

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

413 остаётся после сжатия?

Проверьте разрешение в пикселях, число страниц PDF и рост после Base64. Иногда размер файла в норме, но разрешение после декодирования превышает лимит — сначала уменьшите изображение, затем отправляйте.

429 повторяется, хотя повторы включены?

Посмотрите, не повторяют ли все клиенты одновременно и не превышен ли лимит параллелизма. Случайный разброс, очередь и ограничение параллелизма обычно помогают лучше, чем простое увеличение числа повторов.

Изображение читается, почему результат пустой?

Проверьте ориентацию, язык, контраст и не читаете ли вы неверное поле ответа. Чёткое, но наклонное, бликующее изображение или несовпадение языка всё равно может дать пустой результат.

Повторы приводят к двойной оплате?

Зависит от правил тарификации провайдера и от того, был ли запрос уже обработан. Для тайм-аутов, которые могли завершиться успешно, сначала проверьте статус задачи или сверьтесь по request ID и только потом решайте о повторе.

Что дальше

Когда диагностика стабилизировалась, сведите вызовы OCR в единый клиентский слой: общие тайм-ауты, повторы, журналы и деградация — вместо отдельной реализации в каждом модуле. Если нужно единообразно подключить несколько визуальных моделей, посмотрите шлюз ZSQH AI Gateway; регистрация — на странице /sign-up, модели и биллинг — на странице тарифов.

Читайте также: