Troubleshooting

Обработка reCAPTCHA v2 и v3 на одной странице

Если на странице подключены и reCAPTCHA v2, и v3, решать нужно не обе сразу: сначала v3, и только при низком score — переключаться на v2. v3 работает в фоне и молча выставляет оценку риска, а v2 всплывает как чек-бокс, когда оценка не дотягивает до порога сайта. Для автоматизации это два набора ключей и логика ветвления, которую нужно повторить самостоятельно.


Зачем сайты совмещают v2 и v3

User visits page
    ↓
reCAPTCHA v3 runs invisibly in background
    ↓
Score returned to server (e.g., 0.4)
    ↓
Score below threshold (e.g., < 0.7)?
    ├─ YES → Show reCAPTCHA v2 checkbox/image challenge
    └─ NO  → Allow action without visible CAPTCHA

Такая связка даёт сайту лучшее из обоих подходов:

Сценарий Что происходит
Высокий score v3 CAPTCHA не показывается — минимум трения при входе или регистрации
Низкий score v3 Показывается задача v2 — резервный уровень защиты
Порог перехода Задаёт владелец сайта — он же решает, что считать «подозрительным»

Как выглядит связка v2 + v3 в коде

Вариант 1: сначала v3, затем v2 при низком score

<!-- Both scripts loaded -->
<script src="https://www.google.com/recaptcha/api.js?render=V3_SITE_KEY"></script>
<script src="https://www.google.com/recaptcha/api.js?render=explicit" async defer></script>

<form id="loginForm">
    <!-- v2 widget (hidden initially) -->
    <div id="recaptcha-v2-container" style="display:none;">
        <div class="g-recaptcha" data-sitekey="V2_SITE_KEY"></div>
    </div>
    <button type="submit">Login</button>
</form>

<script>
// First attempt: v3 invisible
grecaptcha.ready(function() {
    grecaptcha.execute('V3_SITE_KEY', {action: 'login'}).then(function(v3Token) {
        fetch('/api/verify-v3', {
            method: 'POST',
            body: JSON.stringify({token: v3Token})
        })
        .then(r => r.json())
        .then(data => {
            if (data.score < 0.7) {
                // Score too low → show v2 fallback
                document.getElementById('recaptcha-v2-container').style.display = 'block';
                grecaptcha.render('recaptcha-v2-container', {sitekey: 'V2_SITE_KEY'});
            } else {
                // Score OK → submit form directly
                document.getElementById('loginForm').submit();
            }
        });
    });
});
</script>

Если ответ приходит с задержкой

Разводите таймаут /api/verify-v3 и таймаут решения токена в CaptchaAI — иначе виджет v2 может отрисоваться дважды при повторной отправке формы.

Вариант 2: разные ключи сайта под разные действия

Homepage → v3 only (passive score)
Login page → v3 assessment, v2 fallback
Checkout → v2 always (high security)
Contact form → v3 only

Вариант 3: один скрипт на оба режима

<script src="https://www.google.com/recaptcha/api.js?render=V3_SITE_KEY"></script>
<script>
    // v3 execute
    grecaptcha.execute('V3_SITE_KEY', {action: 'login'});

    // v2 render (uses a different site key)
    grecaptcha.render('v2-container', {sitekey: 'V2_SITE_KEY'});
</script>

Как найти на странице оба ключа — v2 и v3

Прежде чем писать логику решения, вытащите со страницы оба site key: у v3 и у v2 они разные.

Определение на Python

import requests
import re

def detect_dual_recaptcha(url):
    """Detect if a page uses both reCAPTCHA v2 and v3."""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 (KHTML, like Gecko) "
                      "Chrome/120.0.0.0 Safari/537.36",
    }
    html = requests.get(url, headers=headers, timeout=15).text

    result = {
        "has_v3": False,
        "has_v2": False,
        "v3_site_key": None,
        "v2_site_key": None,
        "dual": False,
        "pattern": None,
    }

    # Detect v3 (render parameter or enterprise.execute)
    v3_match = re.search(r"api\.js\?render=([A-Za-z0-9_-]+)", html)
    if v3_match and v3_match.group(1) != "explicit":
        result["has_v3"] = True
        result["v3_site_key"] = v3_match.group(1)

    # Detect v3 in execute calls
    v3_execute = re.search(
        r"grecaptcha\.(?:enterprise\.)?execute\s*\(\s*['\"]([^'\"]+)['\"]",
        html,
    )
    if v3_execute:
        result["has_v3"] = True
        if not result["v3_site_key"]:
            result["v3_site_key"] = v3_execute.group(1)

    # Detect v2 (g-recaptcha class or explicit render)
    v2_match = re.search(r'data-sitekey="([^"]+)"', html)
    if v2_match:
        key = v2_match.group(1)
        if key != result.get("v3_site_key"):
            result["has_v2"] = True
            result["v2_site_key"] = key

    # Check for explicit v2 render
    v2_render = re.search(
        r"grecaptcha\.render\s*\([^,]+,\s*\{[^}]*sitekey:\s*['\"]([^'\"]+)",
        html,
    )
    if v2_render:
        result["has_v2"] = True
        if not result["v2_site_key"]:
            result["v2_site_key"] = v2_render.group(1)

    result["dual"] = result["has_v3"] and result["has_v2"]

    if result["dual"]:
        # Determine pattern
        if "display:none" in html or "display: none" in html:
            result["pattern"] = "v3_pre_assessment_v2_fallback"
        else:
            result["pattern"] = "v2_v3_simultaneous"

    return result

detection = detect_dual_recaptcha("https://staging.example.com/qa-login")
print(detection)

Определение на Node.js

const axios = require("axios");

async function detectDualRecaptcha(url) {
    const { data: html } = await axios.get(url, { timeout: 15000 });

    const result = {
        hasV3: false,
        hasV2: false,
        v3SiteKey: null,
        v2SiteKey: null,
        dual: false,
    };

    // v3 detection
    const v3Match = html.match(/api\.js\?render=([A-Za-z0-9_-]+)/);
    if (v3Match && v3Match[1] !== "explicit") {
        result.hasV3 = true;
        result.v3SiteKey = v3Match[1];
    }

    // v2 detection
    const v2Match = html.match(/data-sitekey="([^"]+)"/);
    if (v2Match && v2Match[1] !== result.v3SiteKey) {
        result.hasV2 = true;
        result.v2SiteKey = v2Match[1];
    }

    result.dual = result.hasV3 && result.hasV2;
    return result;
}

detectDualRecaptcha("https://staging.example.com/qa-login").then(console.log);

Стратегии решения v2 и v3 через API CaptchaAI

  1. Сначала v3, v2 — только при фолбэке (самая точная).
  2. Пропустить v3, сразу решать v2 (самая экономная).
  3. Определять сценарий через браузер (сложная логика).

Стратегия 1: сначала v3, v2 — только при срабатывании фолбэка

Такой порядок точнее всего повторяет логику самого сайта:

import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_v3(site_key, page_url, action="login"):
    """Solve reCAPTCHA v3 and return token."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": site_key,
        "pageurl": page_url,
        "version": "v3",
        "action": action,
        "json": 1,
    }).json()

    task_id = submit["request"]

    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id, "json": 1,
        }).json()
        if result.get("status") == 1:
            return result["request"]

    raise TimeoutError("v3 solve timeout")

def solve_v2(site_key, page_url):
    """Solve reCAPTCHA v2 and return token."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": site_key,
        "pageurl": page_url,
        "json": 1,
    }).json()

    task_id = submit["request"]

    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id, "json": 1,
        }).json()
        if result.get("status") == 1:
            return result["request"]

    raise TimeoutError("v2 solve timeout")

def solve_dual_recaptcha(v3_key, v2_key, page_url, action="login"):
    """Handle dual reCAPTCHA: try v3, fall back to v2."""
    # Step 1: Try v3
    v3_token = solve_v3(v3_key, page_url, action)

    # Step 2: Submit v3 token to target
    response = requests.post(f"{page_url}/verify", data={
        "g-recaptcha-response": v3_token,
    })

    # Step 3: Check if v2 fallback is needed
    if "recaptcha" in response.text.lower() and v2_key:
        print("v3 score too low — v2 fallback triggered")
        v2_token = solve_v2(v2_key, page_url)
        return {"version": "v2", "token": v2_token}

    return {"version": "v3", "token": v3_token}

result = solve_dual_recaptcha(
    v3_key="6LcExample_v3_key",
    v2_key="6LcExample_v2_key",
    page_url="https://staging.example.com/qa-login",
)
print(f"Solved with {result['version']}")

Стратегия 2: пропустить v3 и сразу решать v2

Если по опыту вы знаете, что для автоматизированного трафика сайт всегда показывает v2 (а score v3 стабильно ниже порога), решение v3 можно вообще не запускать:

# If you consistently fail v3 assessment, just solve v2 directly
token = solve_v2(v2_site_key, page_url)
submit_form(token)

Так вы не платите за решение v3, которое всё равно не пройдёт порог, и не тратите лишний раунд запрос-ответ.

Стратегия 3: определять сценарий через браузер

Для сайтов со сложной логикой удобнее не гадать по HTML заранее, а проверить прямо в браузере, какой виджет реально показан:

from selenium import webdriver
from selenium.webdriver.common.by import By
import time

driver = webdriver.Chrome()
driver.get("https://staging.example.com/qa-login")
time.sleep(3)

# Check if v2 widget is visible
v2_visible = driver.execute_script("""
    const container = document.querySelector('.g-recaptcha');
    if (!container) return false;
    const style = window.getComputedStyle(container.parentElement);
    return style.display !== 'none' && style.visibility !== 'hidden';
""")

if v2_visible:
    # v2 is showing — solve v2
    sitekey = driver.find_element(
        By.CSS_SELECTOR, "[data-sitekey]"
    ).get_attribute("data-sitekey")
    token = solve_v2(sitekey, driver.current_url)
    driver.execute_script(
        f'document.getElementById("g-recaptcha-response").value = "{token}";'
    )
else:
    # v3 only — solve v3
    # Extract v3 key from page source
    v3_key = driver.execute_script(
        "return document.querySelector('script[src*=\"render=\"]')"
        ".src.match(/render=([^&]+)/)[1];"
    )
    token = solve_v3(v3_key, driver.current_url)
    # Inject v3 token into the form
    driver.execute_script(f"""
        const input = document.createElement('input');
        input.type = 'hidden';
        input.name = 'g-recaptcha-response';
        input.value = '{token}';
        document.querySelector('form').appendChild(input);
    """)

driver.find_element(By.CSS_SELECTOR, "form").submit()

Пример: QA-стенд с двойной reCAPTCHA

С этим чаще всего сталкивается команда, гоняющая регресс-тесты логина на стейджинге: репутация IP у v3 проседает от повторов, и после 20–30 попыток подряд всплывает v2. Для такого объёма хватает плана BASIC ($15/мес, 5 потоков).

Если форма собирает email или телефон, держите в уме 152-ФЗ: стенд должен работать только с данными, которые вы вправе обрабатывать.


Особые случаи, которые ломают наивную логику

  1. Два разных site key — легко перепутать.
  2. Enterprise-скоринг v3 с обычным v2 для задачи.
  3. Несколько форм — у каждой свой экземпляр.

Два site key на одной странице

Ключ Где искать
v3 В URL скрипта ?render=KEY и в вызове grecaptcha.execute('KEY', ...)
v2 В атрибуте data-sitekey="KEY" на виджете

Перепутаете ключи местами — получите невалидный токен.

reCAPTCHA Enterprise: скоринг v3, задача — обычный v2

# Detect and handle Enterprise + v2 combo
if "recaptcha/enterprise.js" in html:
    # Use enterprise parameter for v3
    v3_params = {"enterprise": 1, "version": "v3"}
else:
    v3_params = {"version": "v3"}

Несколько форм на одной странице

Если на странице несколько форм — например, логин и регистрация, — у каждой обычно свой экземпляр reCAPTCHA. Вытаскивайте site key из конкретной формы, с которой работаете, а не первый попавшийся на странице:

# Target the login form specifically
login_form = soup.find("form", id="login-form")
widget = login_form.find(attrs={"data-sitekey": True})
sitekey = widget["data-sitekey"]

Частые вопросы про reCAPTCHA v2 и v3

Нужно ли решать и v2, и v3 на одной странице?

Обычно нет. Сначала автоматически срабатывает v3; если его score проходит порог сайта, задача v2 не появляется вовсе. Решать v2 нужно только тогда, когда фолбэк уже сработал.

Как отличить, что сработал фолбэк v2, от того, что сломалась интеграция?

Смотрите на ответ сервера сразу после отправки токена v3. HTML виджета v2 или редирект на шаг с CAPTCHA — это штатный фолбэк. Пустой ответ, код 500 или редирект не на форму — ищите проблему в самой интеграции, например в устаревшем site key.

Можно ли повлиять на порог, при котором вместо v3 показывается v2?

Нет, порог задаёт владелец сайта. Со стороны автоматизации остаётся только закладывать в сценарий, что фолбэк v2 способен сработать, и не считать это ошибкой.

Что делать, если v2 всплывает через раз при одинаковых условиях?

Это нормально: score v3 пересчитывается для каждой сессии заново и зависит от истории IP и поведения браузера — сигналов, которые вы не контролируете напрямую.

Закладывайте в логику оба исхода, а не только «счастливый путь» без v2.

Какой site key использовать для v3, а какой для v2?

Версия Site key
v3 api.js?render=V3_KEY в URL скрипта
v2 data-sitekey="V2_KEY" в HTML виджета

Это всегда два разных значения, даже если оба виджета встроены в одну форму.


Коротко о главном

Двойная reCAPTCHA — это v3 для незаметной оценки и v2 как запасной уровень при низком score. Обнаружить связку помогают два признака: render= в URL скрипта (v3) и data-sitekey на виджете (v2). Рабочая стратегия — решать v3 первым через CaptchaAI и переходить к v2, только если сайт запросил фолбэк. У каждой версии свой ключ и отдельный вызов API.

Похожие статьи

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