API Tutorials

Пользовательские настройки тайм-аута для разных типов CAPTCHA

Единый тайм-аут на все типы CAPTCHA — частая причина ложных ошибок в логах автоматизации. Изображение или OCR-капча обычно решается за 2–5 секунд, а reCAPTCHA Enterprise может занять почти полминуты: если задать одно и то же число для обоих случаев, в первом сценарии клиент лишние 100+ секунд ждёт результат уже упавшей задачи, а во втором — обрывает опрос на честном, но чуть более медленном решении.

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


Тайм-ауты и интервалы опроса по типам CAPTCHA

Значения ниже — отправная точка: начальная задержка перед первым опросом res.php, интервал между повторными опросами и максимальный тайм-аут, после которого имеет смысл считать задачу зависшей.

Тип CAPTCHA Время решения (спец.) Начальная задержка Интервал опроса Максимальный тайм-аут
Изображение / OCR < 0.5 с 1 с 2 с 30 с
reCAPTCHA v2 < 60 с 10 с 5 с 90 с
reCAPTCHA v3 < 4 с 3 с 2 с 60 с
reCAPTCHA Enterprise < 60 с 10 с 5 с 120 с
Невидимая reCAPTCHA < 30 с 8 с 5 с 90 с
Cloudflare Turnstile < 10 с 3 с 3 с 45 с
Cloudflare Challenge < 15 с 8 с 5 с 120 с
GeeTest v3 < 12 с 5 с 5 с 60 с
BLS < 1 с 1 с 2 с 45 с
> Общее правило: максимальный тайм-аут стоит держать примерно вдвое больше среднего времени решения из этой таблицы, а не какое-то одно «универсальное» число вроде 60 или 90 секунд для всех методов сразу.

Типичные ошибки настройки тайм-аутов

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

Проблема Причина Исправление
Тайм-аут для изображений стоит 120 с Значение выбрано «на всякий случай», без привязки к реальному времени решения Снизьте до 30 секунд для изображений — дольше эта задача решаться не должна
reCAPTCHA v2 регулярно уходит в тайм-аут Максимальный тайм-аут занижен относительно реального времени решения Используйте не меньше 90 секунд для reCAPTCHA v2
Первый опрос почти всегда возвращает CAPCHA_NOT_READY Начальная задержка перед первым опросом слишком короткая Увеличьте initial_wait по значению из таблицы для конкретного типа
Слишком много запросов на опрос за секунду Интервал опроса выставлен без учёта типа CAPTCHA Берите 5 секунд для токен-based типов (reCAPTCHA, Turnstile) и 3 секунды для изображений

Тайм-ауты при нестабильной сети: пример для распределённой команды

Если инфраструктура для парсинга или QA-тестов развёрнута не в США, а в европейском дата-центре или на хостинге в Казахстане и Центральной Азии — обычная ситуация для команд, которые работают с русскоязычной аудиторией из России, Беларуси, Казахстана и Украины, — сетевая задержка до ocr.captchaai.com и до целевого сайта добавляет к каждому запросу лишние 100–300 мс. На стабильном проводном канале это несущественно. Но если часть трафика идёт через мобильные сети или спутниковый интернет (частый случай при тестировании форм из регионального офиса или на удалённой точке), первый опрос res.php иногда попадает на CAPCHA_NOT_READY просто из-за задержки сети, а не потому что CAPTCHA ещё не решена.

Практический совет для такого сценария: не увеличивайте max_timeout из таблицы выше — это только продлевает ожидание при реальном сбое. Вместо этого добавьте 1–2 секунды к initial_wait для reCAPTCHA и Turnstile, чтобы первый опрос не тратился впустую на заведомо «не готовый» ответ, и оставьте poll_interval без изменений — частый опрос на медленном канале только увеличивает число запросов без пользы.


Решатель с учётом типа CAPTCHA

Ниже — пример на Python, где тайм-ауты и интервалы опроса привязаны к методу CAPTCHA через словарь конфигурации. Функция solve() сама выбирает нужный набор значений по параметру method (и по version, если это reCAPTCHA v3), отправляет задачу на in.php и опрашивает res.php до получения результата или истечения тайм-аута.

import requests
import time

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://ocr.captchaai.com"

# Per-type timeout configuration
TIMEOUT_CONFIG = {
    "base64": {
        "initial_wait": 1,
        "poll_interval": 2,
        "max_timeout": 30,
    },
    "userrecaptcha": {
        "initial_wait": 10,
        "poll_interval": 5,
        "max_timeout": 90,
    },
    "userrecaptcha_v3": {
        "initial_wait": 3,
        "poll_interval": 2,
        "max_timeout": 60,
    },
    "turnstile": {
        "initial_wait": 3,
        "poll_interval": 3,
        "max_timeout": 45,
    },
    "cloudflare_challenge": {
        "initial_wait": 8,
        "poll_interval": 5,
        "max_timeout": 120,
    },
    "geetest": {
        "initial_wait": 5,
        "poll_interval": 5,
        "max_timeout": 60,
    },
    "bls": {
        "initial_wait": 1,
        "poll_interval": 2,
        "max_timeout": 45,
    },
    "default": {
        "initial_wait": 10,
        "poll_interval": 5,
        "max_timeout": 120,
    },
}


def get_config_key(method, **params):
    """Determine config key from method and parameters."""
    if method == "userrecaptcha" and params.get("version") == "v3":
        return "userrecaptcha_v3"
    return method


def solve(method, **params):
    """Solve CAPTCHA with type-appropriate timeouts."""
    config_key = get_config_key(method, **params)
    config = TIMEOUT_CONFIG.get(config_key, TIMEOUT_CONFIG["default"])

    # Submit task
    data = {"key": API_KEY, "method": method, "json": 1}
    data.update(params)
    resp = requests.post(f"{BASE_URL}/in.php", data=data, timeout=30)
    result = resp.json()

    if result.get("status") != 1:
        raise RuntimeError(f"Submit error: {result.get('request')}")

    task_id = result["request"]

    # Wait before first poll
    time.sleep(config["initial_wait"])

    # Poll with type-specific interval and timeout
    start = time.time()
    while time.time() - start < config["max_timeout"]:
        resp = requests.get(f"{BASE_URL}/res.php", params={
            "key": API_KEY, "action": "get",
            "id": task_id, "json": 1,
        }, timeout=15)
        data = resp.json()

        if data["request"] != "CAPCHA_NOT_READY":
            elapsed = time.time() - start + config["initial_wait"]
            print(f"Solved {method} in {elapsed:.1f}s")
            return data["request"]

        time.sleep(config["poll_interval"])

    raise TimeoutError(
        f"{method} timeout after {config['max_timeout']}s"
    )


# Usage — each type uses optimal timeouts automatically
# Image (fast: 3s wait, 3s poll, 30s max)
token = solve("base64", body=base64_image)

# reCAPTCHA v2 (medium: 10s wait, 5s poll, 90s max)
token = solve("userrecaptcha", googlekey="KEY", pageurl="https://example.com")

# Turnstile (fast: 3s wait, 3s poll, 45s max)
token = solve("turnstile", sitekey="KEY", pageurl="https://example.com")

Адаптивная регулировка тайм-аута по истории решений

Фиксированные значения из таблицы — хороший старт, но реальное время решения плавает в зависимости от нагрузки и конкретного сайта. Класс ниже запоминает время решения для каждого метода и, накопив достаточно наблюдений, сам поднимает max_timeout до удвоенного 95-го перцентиля — так тайм-аут подстраивается под фактическое поведение, а не под табличное среднее.

import statistics


class AdaptiveTimeoutSolver:
    """Adjusts timeouts based on historical solve times."""

    def __init__(self, api_key):
        self.api_key = api_key
        self.base = "https://ocr.captchaai.com"
        self.history = {}  # method -> [solve_times]

    def solve(self, method, **params):
        config = self._get_config(method)

        # Submit
        data = {"key": self.api_key, "method": method, "json": 1}
        data.update(params)
        resp = requests.post(f"{self.base}/in.php", data=data, timeout=30)
        task_id = resp.json()["request"]

        time.sleep(config["initial_wait"])
        start = time.time()

        # Poll with adaptive timeout
        while time.time() - start < config["max_timeout"]:
            resp = requests.get(f"{self.base}/res.php", params={
                "key": self.api_key, "action": "get",
                "id": task_id, "json": 1,
            })
            data = resp.json()

            if data["request"] != "CAPCHA_NOT_READY":
                elapsed = time.time() - start + config["initial_wait"]
                self._record(method, elapsed)
                return data["request"]

            time.sleep(config["poll_interval"])

        raise TimeoutError(f"Timeout after {config['max_timeout']}s")

    def _get_config(self, method):
        """Get timeout config, adjusted by history."""
        base = TIMEOUT_CONFIG.get(method, TIMEOUT_CONFIG["default"])

        # If we have history, adjust max_timeout
        times = self.history.get(method, [])
        if len(times) >= 5:
            p95 = sorted(times)[int(len(times) * 0.95)]
            adjusted_timeout = max(p95 * 2, base["max_timeout"])
            return {**base, "max_timeout": adjusted_timeout}

        return base

    def _record(self, method, elapsed):
        if method not in self.history:
            self.history[method] = []
        self.history[method].append(elapsed)
        # Keep last 100 entries
        if len(self.history[method]) > 100:
            self.history[method] = self.history[method][-100:]

    def get_stats(self, method):
        times = self.history.get(method, [])
        if not times:
            return None
        return {
            "count": len(times),
            "mean": statistics.mean(times),
            "median": statistics.median(times),
            "p95": sorted(times)[int(len(times) * 0.95)],
            "max": max(times),
        }


# Usage
solver = AdaptiveTimeoutSolver("YOUR_API_KEY")
token = solver.solve("turnstile", sitekey="KEY", pageurl="https://example.com")
print(solver.get_stats("turnstile"))

Тайм-аут отправки задачи и тайм-аут ожидания результата

Это два независимых значения, и путать их не стоит: тайм-аут отправки касается только вызова in.php и должен быть коротким и фиксированным, а тайм-аут опроса зависит от типа CAPTCHA и меняется по таблице выше.

Submit timeout: How long to wait for the API to accept your task
  → Set to 30s (network issues only)

Poll timeout: How long to wait for the solve result
  → Varies by CAPTCHA type (30s to 120s)
# Submit timeout (fixed, short)
resp = requests.post(
    f"{BASE_URL}/in.php", data=data,
    timeout=30,  # 30s is plenty for submission
)

# Poll timeout (varies by type)
resp = requests.get(
    f"{BASE_URL}/res.php", params=params,
    timeout=15,  # 15s per individual poll request
)
# Overall polling loop timeout: 30-120s depending on type

Часто задаваемые вопросы

Ниже — вопросы, которые чаще всего возникают уже после того, как таблица тайм-аутов внедрена в код, а не до этого.

Почему нельзя ставить один тайм-аут на все типы CAPTCHA?

Потому что разброс времени решения между типами — от 2–5 секунд для изображения до 25 секунд для reCAPTCHA Enterprise. Тайм-аут в 120 секунд для картинок означает, что при сбое вы впустую ждёте больше минуты; тайм-аут в 30 секунд для reCAPTCHA Enterprise, наоборот, может оборвать честное, но чуть более медленное решение.

Как подобрать тайм-аут для типа CAPTCHA, которого нет в таблице?

Возьмите среднее время решения похожего по сложности типа (например, GeeTest v3 близок по времени к reCAPTCHA v3) и умножьте примерно на два — это даёт запас на сетевые задержки и разброс без лишнего ожидания при реальном сбое. После первых 50–100 решений посмотрите на фактический p95 и скорректируйте значение вручную или через адаптивный класс из раздела выше.

res.php стабильно возвращает CAPCHA_NOT_READY дольше, чем указано в max_timeout — что не так?

Обычно это значит, что тайм-аут выставлен ниже реального времени решения для этого типа CAPTCHA, а не что задача зависла. Задача при этом может продолжать решаться на стороне CaptchaAI — вы просто перестаёте её опрашивать раньше времени. Сверьтесь с таблицей выше и увеличьте max_timeout минимум вдвое относительно среднего времени решения.

Стоит ли увеличивать тайм-ауты в часы пик или при нестабильной мобильной сети?

Сам тайм-аут решения — нет: скорость решения CAPTCHA на стороне CaptchaAI не привязана к часам пик. А вот initial_wait действительно стоит немного увеличить, если запросы идут через нестабильный или мобильный канал (см. пример с распределённой командой выше) — так первый опрос реже попадает на заведомо «не готовый» ответ.

Нужно ли отдельно настраивать тайм-аут для in.php?

Да, и это не то же самое, что тайм-аут опроса. in.php только принимает задачу — 30 секунд с запасом хватает на любые сетевые накладки. Все различия между типами CAPTCHA относятся к тайм-ауту опроса res.php, а не к отправке.


Похожие руководства

Настройка тайм-аутов обычно идёт в паре с двумя соседними темами — ограничением частоты запросов и логикой повторных попыток:


Настройте тайм-ауты по таблице выше и запустите первый запрос к API CaptchaAI — это займёт около пяти минут.

Комментарии для этой статьи отключены.