Captcha: явные задания в Python
Через ctx.captcha() можно узнать параметры задания, оценить стоимость, явно отправить его и получить ответ. Этот API не открывает сайт, не обнаруживает новую CAPTCHA, не заполняет форму и не применяет ответ в браузере. Тип задания определяет входные поля и формат ответа. В преобразованиях выгрузки, где запрещены host calls, эти методы недоступны.
Контракт одинаков в облаке и на собственном агенте. В сценарии не нужны внешний аккаунт, ключ сервиса или платёжный токен. Работа учитывается по тарифу и балансу пространства сайта. Агенту требуется связь с Akmelo; без облака эти операции недоступны.
Перед отправкой
Начните с types() и describe(type): они читают актуальный каталог без платного задания. После подготовки полей вызовите quote(task). Задание создаёт только createTask(); в Python используется create_task(). Оценка стоимости сама по себе не резервирует деньги и не фиксирует будущую цену. Для создания CAPTCHA должна быть разрешена настройками сайта и платформы.
Не отправляйте пароли, cookies авторизации, персональные данные и постороннее содержимое страницы. Передавайте только поля из describe(type). Произвольные адреса API, выбор внешнего сервиса, ключи и callback не являются настройками задания. Разным типам нужны разные данные; их нельзя просто переименовать друг в друга.
В GridTask.previousId передавайте наш taskId предыдущего готового GridTask того же сайта и пространства, а на агенте — также того же агента. Платформа сама разрешает внутреннюю ссылку; внешний идентификатор или чужое задание отклоняются до отправки.
Методы
types()describe()quote()create_task()get_task_result()wait_for_result()report_correct()report_incorrect()
types
types() -> dict[str, Any]
Возвращает {types: [...], languages: [...], unit: 1000000}. Каждый элемент каталога описывает доступный тип; languages содержит допустимые коды языковых пулов. Список актуален для этой установки: не храните собственную копию названий как источник истины. Включённость тарифа и остатки проверяются отдельно при создании.
describe
describe(type) -> dict[str, Any]
Возвращает описание одного типа: type, name, kind, parameters, solution и ценовую информацию. parameters содержит допустимые поля, типы, обязательность и пределы; solution описывает форму ответа. Неизвестный тип отклоняется. Например, ImageToTextTask принимает base64 в body, а AudioTask — аудио в body и язык аудио в lang (это отдельное поле от options.languagePool); точные ограничения читайте в схеме.
В некоторых описаниях есть required_any: кроме обычных обязательных полей нужно заполнить хотя бы один полный альтернативный набор. requirements описывает условные требования, например для разных версий задания. Одного списка необязательных полей недостаточно для правильной отправки. В price значение quoted: false означает технический резерв вместо подтверждённого диапазона цены; available говорит о доступности цены, а не об успешном доступе к сайту.
quote
quote(task) -> dict[str, Any]
Проверяет поля и возвращает {minimum, maximum, unit: 1000000}. Суммы в микро₽: 1 000 000 единиц = 1 ₽. minimum: null означает, что нижняя граница не определена; это не нулевая цена. maximum — верхняя оценка для предварительного резерва. Включённые в тариф задания и разрешённые дополнительные расходы всё равно проверяются сервером.
create_task
create_task(task, options=None) -> dict[str, Any]
Создаёт явное задание. Результат: {taskId, status}, где taskId — UUID платформы, при новой отправке статус processing либо uncertain. Повтор с тем же ключом запроса может вернуть уже готовое или неуспешное состояние ready/failed. Не передавайте сюда идентификатор задания из другой системы. Неизвестные типы, поля и некорректные значения отклоняются до отправки. Возврат uncertain означает неопределённость отправки: сохраните идентификатор и проверяйте его, а не создавайте новый платный запрос.
get_task_result
get_task_result(task_id) -> dict[str, Any]
Читает состояние существующего задания, не создавая новое. Возвращает taskId, status, unit и, когда доступны, solution, cost, error. Формат solution зависит от типа: это JSON-значение, в том числе объект, массив, строка, число, логическое значение или null. Не считайте наличие текстового поля обязательным: ответ может содержать координаты, угол, токен или другую описанную структуру. Сверяйтесь с JSON Schema solution в описании типа. При ошибке используйте безопасное поле error для диагностики.
wait_for_result
wait_for_result(task_id, timeout_ms=120000) -> dict[str, Any]
Ожидает результат существующего задания. timeout_ms от 0 до 120000; 0 даёт однократную проверку. По окончании ожидания возвращается последнее состояние, в том числе processing или uncertain. Это не отменяет задание и не создаёт повтор. Остановка или общий срок прогона прерывают ожидание ошибкой. Чтобы продолжить позже, сохраните taskId и вызовите получение результата снова.
report_correct
report_correct(task_id) -> dict[str, Any]
Отправляет обратную связь об уже проверенном правильном ответе. Возвращает {taskId, accepted}. accepted: true означает приём отзыва, а не автоматическую проверку сайта. Не отправляйте положительный отзыв просто потому, что статус стал ready.
report_incorrect
report_incorrect(task_id) -> dict[str, Any]
Отправляет обратную связь об уже проверенном неправильном ответе. Возвращает {taskId, accepted}. Приём отзыва не обещает возврат денег, не отменяет задание и не запускает новое. Отказ сайта по другой причине не доказывает ошибку результата CAPTCHA.
Параметры создания
Ключи options одинаковы во всех языках; snake_case в Python относится к названиям методов.
| Поле | Значение | Для чего |
|---|---|---|
requestId |
1–128 символов: A–Z, a–z, 0–9, ., _, :, - |
Постоянный ключ идемпотентности одной логической отправки в пределах того же прогона. При её повторе используйте тот же ключ; для действительно нового входа нужен новый. В новом прогоне прежний ключ не переиспользует старую отправку: читайте сохранённый taskId, а не создавайте её снова. |
language |
ru или en |
Короткая форма выбора языкового пула. |
languagePool |
Код из types().languages, например zh |
Языковой пул инструкций задания. Это не язык сайта; не сочетайте его с отличающимся значением language. |
maxCost |
Неотрицательное целое в микро₽ | Ваш потолок на задание. Если системный резерв выше него, отправка отклоняется. Параметр не меняет цену задания. |
Статусы и оплата
| Статус | Что делать |
|---|---|
processing |
Ожидать по этому идентификатору. |
ready |
Читать ответ и отдельно проверять его применение. |
failed |
Разобраться в ошибке, не запускать бесконечный повтор. |
uncertain |
Отправка либо её итог не подтверждены. Повторно проверить этот же идентификатор. |
Готовый ответ может быть платным, даже если сайт его отверг. Действуют обычные для пространства количество CAPTCHA в тарифе, баланс, резервы и пределы стоимости. Если поле cost есть, это сумма списания пространства в микро₽, а не отдельный счёт другого сервиса. Оценка не равна итоговому списанию; неиспользованный резерв освобождается после сверки. Остановка прогона либо таймаут ожидания не обещают отмену или возврат.
Идентификатор доступен только тому же сайту и пространству. Агент читает только задания своего агента. Другой прогон того же разрешённого сайта может прочитать существующее задание; знание UUID не даёт доступа. Ответ хранится 24 часа. После истечения срока ready может вернуться без solution и с error: result_expired; сохраняйте нужный результат обработки, а не материал для повторной авторизации.
Полный пример со своей фикстурой
Подготовьте небольшую картинку с известным текстом и передайте только её base64 в параметре прогона fixture_image_base64, без URL и префикса data-URL. По умолчанию пример только получает оценку: send_task=false. Устанавливайте send_task=true, только когда хотите отправить платное задание. При смене фикстуры меняйте ключ запроса. Проверка кода проверяет синтаксис и SDK, не отправляя задание.
def run(ctx):
captcha = ctx.captcha()
body = ctx.param('fixture_image_base64', '')
if not body:
ctx.abort('Укажите base64 собственного тестового изображения')
task = {'type': 'ImageToTextTask', 'body': body}
price = captcha.quote(task)
ctx.log('Максимальный резерв: ' + str(price['maximum']) + ' микро₽')
if not ctx.param('send_task', False):
return
created = captcha.create_task(task, {
'requestId': 'fixture-image-001',
'language': 'ru',
'maxCost': price['maximum'],
})
result = captcha.wait_for_result(created['taskId'], 120000)
solution = result.get('solution')
if result['status'] == 'ready' and isinstance(solution, dict) and isinstance(solution.get('text'), str):
ctx.emit({'text': solution['text']}, 'fixture-001')
else:
ctx.log('Состояние задания: ' + result['status'])
Другие входные данные и ошибки
Для аудио сначала прочитайте describe("AudioTask"): передайте аудио base64 в body и документированное значение lang. Для остальных типов читайте их собственные параметры и схему ответа. Не отправляйте токеновое задание как картинку и не превращайте массив координат в строку. Некорректный base64, лишние поля, размер, отключённая функция, недостаточный бюджет и недоступный идентификатор — разные ошибки. Исправляйте причину; ожидание по имеющемуся UUID отличается от новой отправки.
Этот API не расширяет список типов, автоматически определяемых браузером. autoCaptcha=false (Python: auto_captcha=False) управляет автоматическим HTTP/браузерным режимом и не отключает явное создание. Чтобы исключить платные отправки, не вызывайте метод создания.