Сбои 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.
Подготовку изображений рекомендуем вести в таком порядке:
- Прочитайте файл библиотекой изображений и определите реальный формат — расширению не доверяйте.
- Пропорционально уменьшите изображение по длинной стороне, например до 2000–3000 пикселей, и повторите тест.
- Исправьте ориентацию EXIF, чтобы текст не оказался повёрнутым и распознавание не вернуло пустоту.
- Приведите файл к поддерживаемому формату и подберите качество сжатия, не размывая символы.
- Многостраничный PDF разбирайте постранично или используйте интерфейс для целых документов.
- Записывайте размер и разрешение до и после обработки, чтобы понять, на каком шаге возникло превышение.
Не сжимайте изображение до нечитаемого состояния ради обхода 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, модели и биллинг — на странице тарифов.
Читайте также:
