Решайте и отправляйте reCAPTCHA с aiohttp

Автор: solvethree3 мин на чтение

Используйте aiohttp для прямой работы с REST API: создайте одну задачу, опрашивайте её до готовности, затем отправьте solution.token целевому сайту. Выполняйте запросы к API и целевому сайту в отдельных сессиях, чтобы Bearer-токен рабочего пространства никогда не попал на целевой сайт.

Для примера требуется Python 3.11+; создание, опрос и отправка целевому сайту ограничены общим временем в 180 секунд. Это тайм-аут приложения, а не гарантированное время решения.

На этой странице

Задайте сайт, прокси и целевой запрос

  • Задайте для SOLVETHREE_API_KEY API-ключ рабочего пространства. Храните его на сервере.
  • Задайте для SOLVETHREE_PROXY URL прокси, который сервис должен использовать для решения CAPTCHA. Отправка целевому сайту выполняется через тот же прокси.
  • Замените accounts.example.com и действие login именем хоста и действием, используемыми в вашей интеграции. Для сайта нужен активный профиль, доступный вашему рабочему пространству; настройте профиль перед созданием задач.
  • Замените URL входа на целевой сайт и данные формы фактическим запросом приложения. Пример отправляет только g-recaptcha-response; добавьте остальные поля, необходимые целевому сайту, и используйте ожидаемое им поле токена.

Задача передаёт site, action и один proxy. По умолчанию используется reCAPTCHA Enterprise v3; не добавляйте поля type или url. Если целевому процессу нужны cookies из предыдущего запроса, выполняйте эти запросы через сессию целевого сайта — пример начинает с новой сессии.

Создайте задачу один раз, опросите её и отправьте токен

aiohttp · однократное создание, опрос и отправка
# pip install aiohttp
import asyncio
import os
import uuid
from urllib.parse import urlsplit, unquote
import aiohttp

async def main():
    api = "https://api.solvethree.com/v1"
    proxy = os.environ["SOLVETHREE_PROXY"]
    task = {"site": "accounts.example.com", "action": "login", "proxy": proxy, "requestId": str(uuid.uuid4())}
    task_id = None
    try:
        async with asyncio.timeout(180):
            async with aiohttp.ClientSession(headers={"Authorization": "Bearer " + os.environ["SOLVETHREE_API_KEY"]}) as client:
                async with client.post(api + "/tasks", params={"waitMs": 0}, json=task, allow_redirects=False) as response:
                    response.raise_for_status()
                    if response.status >= 300: raise RuntimeError("Unexpected API redirect")
                    result = await response.json()
                task_id = result["id"]
                while result["status"] in ("pending", "solving"):
                    async with client.get(api + "/tasks/" + task_id, params={"waitMs": 15000}, allow_redirects=False) as response:
                        response.raise_for_status()
                        if response.status >= 300: raise RuntimeError("Unexpected API redirect")
                        result = await response.json()
                    if result["status"] in ("pending", "solving"): await asyncio.sleep(1)
                if result["status"] != "ready":
                    raise RuntimeError("Task failed: " + str(result.get("errorCode", "unknown")))
            solution = result["solution"]
            # A separate session prevents the API Bearer header reaching the target.
            proxy_url = urlsplit(proxy)
            netloc = proxy_url.netloc.rsplit("@", 1)[-1]
            server = f"{proxy_url.scheme}://{netloc}"
            auth = aiohttp.BasicAuth(unquote(proxy_url.username), unquote(proxy_url.password or "")) if proxy_url.username else None
            async with aiohttp.ClientSession() as target:
                async with target.post("https://accounts.example.com/login", proxy=server, proxy_auth=auth,
                    headers={"User-Agent": solution["userAgent"], "sec-ch-ua": solution["secChUa"]},
                    data={"g-recaptcha-response": solution["token"]}, allow_redirects=False) as response:
                    print("Application HTTP status:", response.status)
    except TimeoutError:
        print("Timed out; recover requestId", task["requestId"], "taskId", task_id)
        raise

asyncio.run(main())

Это прямая асинхронная работа с REST, а не асинхронный метод официального Python SDK. Пример выполняет один POST-запрос создания к /v1/tasks, сохраняет возвращённый id и использует GET-запросы для опроса этой задачи. Статусы pending и solving продолжают цикл; ready разрешает отправку; задача со статусом failed останавливает цикл.

Поле proxy в теле задачи указывает solvethree, какой прокси использовать. Аргументы aiohttp proxy и proxy_auth задаются только в HTTP-вызове целевого сайта, а не в вызовах API. Пример отделяет учётные данные прокси от URL прокси-сервера и отправляет токен с возвращёнными userAgent и secChUa.

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

Запрос к API возвращает HTTP-ошибку.

Пример проверяет HTTP-статус перед чтением данных успешного ответа.

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

Задача переходит в статус failed.

Проверьте errorCode и error в ответе задачи. Пример выбрасывает исключение только с errorCode.

Попробуйте: Прекратите опрос этой задачи и устраните указанную причину. Исправьте указанную причину перед повторной попыткой.

Задача готова, но приложение отклоняет запрос.

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

Попробуйте: Отправьте solution.token с возвращёнными параметрами браузера через тот же прокси. Оценивайте ответ приложения по критериям успеха целевого сайта — одного выведенного HTTP-статуса недостаточно, чтобы подтвердить принятие запроса.

Восстановление после тайм-аута без оплаты ещё одного решения

Тайм-аут при создании не доказывает, что создание завершилось неудачно. Пример сохраняет исходные входные данные в task и при истечении отведённого времени выводит его requestId и task_id. Эти переменные сохраняются только во время работы процесса; выведенные ID не составляют полной записи для восстановления.

  1. 1.

    Сохраните исходный запрос перед созданием

    Для восстановления после перезапусков надёжно сохраняйте полное тело задачи, включая requestId и proxy. Пример генерирует новый UUID при каждом запуске, поэтому простой повторный запуск создаёт новый логический запрос.

  2. 2.

    Возобновите работу с существующей задачей

    Если у вас есть task_id, получите эту задачу. Если при создании произошёл тайм-аут до получения ID, повторите создание с сохранёнными неизменёнными телом задачи и requestId. API вернёт существующую задачу; изменение входных данных при повторном использовании ID вызывает конфликт.

  3. 3.

    Проверьте, не была ли отправка уже выполнена

    Общее ограничение времени распространяется и на POST-запрос целевому сайту. Если этот запрос завершился по тайм-ауту, перед повторной отправкой проверьте результат в приложении: оно могло получить запрос, даже если ваш клиент не получил ответ. Восстановление задачи solvethree не позволяет определить, что произошло на целевом сайте.

Запустите первую задачу.

Начните с $5 на балансе рабочего пространства.

Создать рабочее пространство
Решайте и отправляйте reCAPTCHA с aiohttp · solvethree