Как исправить ERROR_UPLOAD при отправке капчи-картинки

ERROR_UPLOAD означает, что до решателя не дошли пригодные данные изображения. Сам запрос был в порядке, и ключ API тоже, но часть, которая должна была нести картинку, пришла пустой либо не пришла вовсе. Это один из четырёх кодов, охватывающих всё, что может пойти не так на входе, и их легко перепутать, поскольку со стороны пользователя все четыре выглядят одинаково. Эта статья разделяет их, даёт реальные ограничения и показывает код загрузки, который не вызывает ни один из них.
Что понадобится
- CapSkip запущен либо в режиме Local на loopback-адресе, либо в режиме Server на машине, которая доступна вашему коду. Оба варианта описаны в разделе Настройки подключения, и выбор ничего не меняет в ошибках ниже.
- Python 3.10 или новее с установленным SDK, либо curl, если вы предпочитаете обращаться к эндпоинту напрямую.
- Одно настоящее изображение капчи, сохранённое на диск. Скриншот целой страницы для этого не подходит: он заставит вас гоняться за лимитом размера, а не искать настоящую ошибку.
Что означает каждый код ошибки изображения
Это коды, которые API возвращает при отправке изображений, прямо из документации CapSkip API. Сначала прочитайте столбец со значением, потому что их различает место сбоя, а не его тяжесть.
| Код | Что это значит | Где возникла проблема |
|---|---|---|
| ERROR_UPLOAD | Данные изображения не переданы, либо загрузка не удалась | Тело запроса. Не пришло ничего пригодного для использования |
| ERROR_INVALID_IMAGE | Неверный формат изображения, либо повреждённые данные изображения | Байты пришли, но не декодируются как изображение |
| ERROR_INVALID_BASE64 | Неверная кодировка base64 | Строка так и не превратилась обратно в байты |
| ERROR_TOO_BIG_CAPTCHA_FILESIZE | Больше 600 кБ, либо больше 1000px по одной из сторон | Изображение настоящее и корректное, просто слишком большое |
| ERROR_BAD_PARAMETERS | Отсутствуют или некорректны обязательные параметры | Поля формы вокруг изображения |
Есть и пятый вид сбоя, который вообще не доходит до API. Передайте SDK несуществующий путь, и он выбросит ValidationException с именем файла, даже не отправив запрос. Это различие стоит усвоить сразу: исключение Python означает, что процесс остановила ваша собственная машина, а код, начинающийся с ERROR, означает, что его остановил решатель.
Шаг 1: отправьте изображение в формате, который принимает API
SDK принимает три формы входных данных и превращает любую из них в одинаковую отправку: локальный путь, удалённый URL или data URI с изображением внутри.
# pip install capskip
from capskip import CapSkip
solver = CapSkip(host="127.0.0.1", port=8080)
# All three of these are accepted by the same method.
result = solver.normal("captcha.png")
result = solver.normal("https://example.com/captcha.jpg")
result = solver.normal("data:image/png;base64,iVBORw0KGgo...")
print(result["code"]) # the recognized textИспользуйте абсолютный путь, а не относительный. Относительный путь разрешается относительно рабочего каталога процесса, который редко совпадает с каталогом, где вы писали код, и именно поэтому скрипт, работающий в редакторе, чаще всего ломается при запуске через планировщик или сервис.
В необработанном HTTP API есть два способа передать изображение, и чаще всего именно неверно выбранное поле приводит к ERROR_UPLOAD. Multipart отправляет сам файл, а base64 отправляет его в виде строки.
# no install needed, curl only # method=post carries the file as multipart/form-data curl -X POST "http://127.0.0.1:8080/in.php" \ -F "key=capskip" \ -F "method=post" \ -F "[email protected]" # method=base64 carries the same image as a string curl -X POST "http://127.0.0.1:8080/in.php" \ -d "key=capskip" \ -d "method=base64" \ --data-urlencode "body=BASE64_STRING_HERE"
Обратите внимание на названия полей. При методе multipart изображение передаётся в поле file, а при методе base64 попадает в поле body. Если отправить файл в поле body, или пропустить символ @, который указывает curl прочитать файл, а не отправить его имя как обычный текст, в обоих случаях получится загрузка без изображения внутри.
Шаг 2: исправьте ERROR_INVALID_BASE64
Сбои base64 почти всегда вызваны переносом строк. GNU coreutils по умолчанию переносит вывод через каждые 76 символов, и эти переводы строк попадают прямо в запрос, поэтому при кодировании в терминале указывайте нулевую ширину переноса. Кодировщики ведут себя по-разному, а некоторые не переносят вовсе, и это довод в пользу того, чтобы кодировать в коде, а не в командной строке.
Ещё одно различие подводит тех, кто переходит между SDK и необработанным API. SDK принимает полный data URI вместе с префиксом. Параметр body в необработанном эндпоинте требует сам base64-payload, без какого-либо префикса перед ним.
# pip install capskip
import base64
with open("captcha.png", "rb") as fh:
raw = base64.b64encode(fh.read()).decode()
# One continuous string. No newlines, no spaces, no truncation.
assert "\n" not in raw and len(raw) > 50
# The SDK wants the prefix, the raw body parameter does not.
result = solver.normal("data:image/png;base64," + raw)Эта проверка длины важнее, чем кажется. Строка base64 короче примерно пятидесяти символов воспринимается как путь к файлу, а не как данные изображения, поэтому усечённый payload вообще не падает как неверный base64. Он падает как отсутствующий файл, из-за чего вы ищете причину не там.
Шаг 3: исправьте ERROR_TOO_BIG_CAPTCHA_FILESIZE
Здесь есть опубликованные цифры, а не расплывчатая рекомендация. API отклоняет изображение больше 600 кБ, а также отклоняет изображение больше 1000px по любой из сторон. Оба лимита щедры для настоящей капчи, поэтому превышение любого из них обычно означает, что вместо виджета ушёл скриншот всей страницы.
Обрезка изображения до границ капчи работает лучше всего, поскольку заодно повышает точность распознавания. Уменьшение масштаба стоит использовать как запасной вариант, если надёжно обрезать не получается.
# pip install capskip pillow
from PIL import Image
img = Image.open("captcha.png")
# Bring the longest side under the documented 1000px ceiling.
if max(img.size) > 1000:
img.thumbnail((1000, 1000))
# optimize=True usually clears the 600 kB limit on its own.
img.save("captcha-ready.png", optimize=True)Шаг 4: исправьте ERROR_INVALID_IMAGE
Этот код означает, что байты пришли целиком, но не являются изображением. На практике у этого есть одна главная причина. Вы скачали капчу через HTTP-клиент, сервер ответил страницей ошибки или перенаправлением, а ваш код сохранил тело ответа в файл с расширением .png, ни разу не заглянув внутрь. Файл настоящий, размер правдоподобный, но первые четыре байта содержат HTML.
# no install needed, standard library only
SIGNATURES = (b"\x89PNG", b"\xff\xd8\xff", b"GIF8", b"RIFF")
with open("captcha.png", "rb") as fh:
head = fh.read(4)
# An error page saved as .png starts with <!DO or <htm instead.
print(any(head.startswith(sig) for sig in SIGNATURES))Выполняйте эту проверку в момент загрузки, а не в момент распознавания. Чтение четырёх байт ничего не стоит и превращает запутанную ошибку решателя в очевидную сетевую ошибку именно там, где эта сетевая ошибка на самом деле произошла.
Полный рабочий пример
Если объединить все четыре исправления, получится функция, которая проверяет данные перед отправкой и отделяет ошибки, возникшие на вашей машине, от ошибок, которые вернул решатель.
# pip install capskip pillow
from PIL import Image
from capskip import CapSkip, ApiException, ValidationException
solver = CapSkip(host="127.0.0.1", port=8080, defaultTimeout=120)
def solve_image(path):
"""Return the CAPTCHA text, or None with a reason printed."""
img = Image.open(path) # raises if it is not an image
if max(img.size) > 1000:
img.thumbnail((1000, 1000))
img.save(path, optimize=True)
try:
return solver.normal(path)["code"]
except ValidationException as exc:
print("never left this machine:", exc)
except ApiException as exc:
print("the solver rejected it:", exc)
return NoneЕсли сначала открыть изображение через Pillow, два из четырёх кодов сворачиваются в одно локальное исключение, ведь файл, который не открывается здесь, всё равно не декодировался бы и там. До API после этого доходит заведомо корректное изображение заведомо подходящего размера.
Одна вещь, которую не стоит добавлять: прокси. Прокси поддерживаются для reCAPTCHA, Turnstile, GeeTest и CaptchaFox, но не для капч-картинок. Передача прокси считается ошибкой параметра, а не молчаливым игнорированием, поэтому запрос сразу завершится ошибкой.
Как запустить решатель на сервере
Среди всех типов капчи именно загрузка изображений несёт в запросе настоящий payload, поэтому стоит знать, куда именно он уходит. CapSkip предлагает два варианта:
| Режим | Прослушивает | Когда использовать |
|---|---|---|
| Локально | 127.0.0.1, только это устройство | Ваш скрипт и решатель работают на одной машине |
| Сервер | Ваш сетевой адрес или публичный IP | Воркеры, VPS или облачная платформа обращаются через API |
В режиме Server вы указываете в SDK host машины с решателем, и больше в коде ничего не меняется, так что один экземпляр может обслуживать сразу пул воркеров. Если обращения приходят извне вашей сети, рекомендуется статический публичный IP. Подробности приведены в разделе Настройки подключения, и режим Server остаётся вашим железом и по-прежнему безлимитным: меняется лишь то, где работает решатель, а не то, кому он принадлежит и как выставляются счета.
FAQ
Какой максимальный размер изображения принимает CapSkip?
600 кБ и 1000px по любой из сторон. Превысьте любой из лимитов, и вместо частичного решения вы получите ERROR_TOO_BIG_CAPTCHA_FILESIZE. Обе цифры опубликованы на странице справочника по API и с запасом превышают размеры любой настоящей капчи, так что их превышение стоит воспринимать как сигнал: вы отправляете больше содержимого страницы, чем нужно.
Почему одно и то же изображение работает локально, но не проходит на сервере?
Обычно дело в пути, а не в изображении. Относительный путь разрешается относительно рабочего каталога, в котором был запущен процесс, а сервис или задача по расписанию редко стартуют там, где вы думаете. Используйте абсолютный путь, а затем проверьте, что файл доступен для чтения той учётной записи, от имени которой работает процесс. Если сам решатель находится на другой машине, убедитесь, что host и port указывают на неё, а не на loopback.
Можно ли использовать прокси при распознавании капч-картинок?
Нет. Поддержка прокси распространяется на reCAPTCHA, Turnstile, GeeTest и CaptchaFox: именно в этих типах решатель сам загружает страницу. Капча-картинка представляет собой байты, которые уже есть у вас на руках, поэтому прокси там нечего делать, и такой параметр не игнорируется, а отклоняется с ошибкой.
Сколько времени должно занимать распознавание капчи-картинки?
При обычной работе это занимает заметно меньше секунды. Таймаут опроса для задач с изображениями по умолчанию равен 120 секундам, и это скорее подстраховка, чем ожидаемое время: SDK начинает опрашивать каждую четверть секунды и постепенно увеличивает интервал. Если вы ждёте секунды, а не миллисекунды, интереснее спросить, чем занят решатель, а не хватает ли вам таймаута.
Самая короткая версия
Сначала разберитесь, какая сторона дала сбой, прежде чем что-либо менять. Исключение Python означает, что изображение вообще не покинуло вашу машину, а код, начинающийся с ERROR, означает, что оно дошло и было отклонено. Затем сопоставьте код с ситуацией: ERROR_UPLOAD означает, что не пришло ничего; ERROR_INVALID_IMAGE означает, что данные пришли, но не декодируются; ERROR_INVALID_BASE64 означает, что не декодируется сама строка; а слишком большой размер означает превышение 600 кБ либо 1000px. Проверяйте байты в момент загрузки, и большинство таких ошибок перестанут возникать. Если распознавание не удалось уже после чистой загрузки, это другая проблема, и она рассмотрена в руководстве по ERROR_CAPTCHA_UNSOLVABLE. Что касается самих параметров, страница сервиса распознавания капч-картинок служит справочником, а особенности для Python приведены на странице сервиса распознавания капч для Python. Экономика тоже играет на руку: безлимитный сервис распознавания капчи , работающий на вашем оборудовании, означает, что повторная попытка после неудачной загрузки не будет стоить ничего, кроме самой попытки.
