Explainers

reCAPTCHA v2 Invisible: обнаружение и решение триггеров

Автотест отправляет форму на staging-стенде, запрос уходит, а бэкенд возвращает ошибку проверки — при этом на странице нет ни флажка, ни картинок с автобусами. Почти всегда это reCAPTCHA v2 Invisible: виджет срабатывает молча в момент отправки и показывает задание только тогда, когда риск-анализ Google считает сессию подозрительной.

Короткий ответ, если вы пришли сюда именно с этой проблемой: ищите атрибут data-size="invisible", значок .grecaptcha-badge в углу экрана и вызовы grecaptcha.execute() в скриптах страницы. Метод в API прежний — userrecaptcha, как и для обычной v2, — но в запрос добавляется invisible=1. Без этого флага решение пойдёт по другой схеме, и сайт отклонит полученный токен. Дальше — четыре способа найти виджет, интеграция с CaptchaAI и разбор частых ошибок.


Пять признаков, по которым тип опознаётся за минуту

  • Флажка на странице нет, но в углу висит небольшой значок reCAPTCHA.
  • У элемента с классом .g-recaptcha выставлен data-size="invisible".
  • Класс g-recaptcha навешен не на div, а прямо на кнопку отправки формы.
  • В инлайновых скриптах встречается grecaptcha.execute() без параметра action.
  • Форма уходит без видимой задержки, и только ответ сервера сообщает, что проверка не пройдена.

Двух совпадений достаточно, чтобы считать тип определённым. Полагаться на один значок не стоит: его часто прячут стилями.


Четыре способа обнаружить Invisible reCAPTCHA

Проверки выполняются в консоли браузера: идите сверху вниз и останавливайтесь на первом способе, который вернул sitekey.

Способ 1: прочитать атрибут data-size

Скрипт перебирает виджеты и сразу печатает ключ сайта для запроса к API.

// Browser console
const widgets = document.querySelectorAll('.g-recaptcha');
widgets.forEach((el, i) => {
  const size = el.getAttribute('data-size');
  const sitekey = el.getAttribute('data-sitekey');
  console.log(`Widget ${i}: size=${size}, sitekey=${sitekey}`);
  if (size === 'invisible') {
    console.log('  → This is Invisible reCAPTCHA');
  }
});

Способ 2: найти значок reCAPTCHA

Invisible-вариант обязан показывать значок в углу. Даже если он скрыт стилями, свойство visibility подтвердит, что элемент на странице есть.

const badge = document.querySelector('.grecaptcha-badge');
if (badge) {
  console.log('reCAPTCHA badge found — likely Invisible reCAPTCHA');
  console.log('Badge visibility:', getComputedStyle(badge).visibility);
}

Способ 3: поймать вызов grecaptcha.execute()

Когда виджет создаётся программно, блока .g-recaptcha в разметке может не быть вовсе. Тогда ключ сайта вытаскивается прямо из инлайнового скрипта регулярным выражением.

// Look for grecaptcha.execute in page scripts
document.querySelectorAll('script:not([src])').forEach(s => {
  if (s.textContent.includes('grecaptcha.execute')) {
    console.log('Found grecaptcha.execute — Invisible reCAPTCHA');
    const match = s.textContent.match(/grecaptcha\.execute\s*\(\s*['"]?([^'",\s)]+)/);
    if (match) console.log('Sitekey:', match[1]);
  }
});

Способ 4: разобрать параметры тега скрипта

Последняя проверка, если предыдущие ничего не дали: параметр render= в URL библиотеки означает, что виджет рендерится неявно — invisible-вариант v2 или v3.

document.querySelectorAll('script[src*="recaptcha"]').forEach(s => {
  if (s.src.includes('render=') && !s.src.includes('render=explicit')) {
    console.log('Invisible/v3 reCAPTCHA detected in script:', s.src);
  }
});

Чем Invisible отличается от обычной reCAPTCHA v2

Различия удобно держать в голове одной таблицей: они напрямую определяют, что писать в запросе и что делать с токеном.

Характеристика reCAPTCHA v2 (обычная) reCAPTCHA v2 Invisible
Видимый виджет Да, флажок Нет, только значок в углу
Действие пользователя Клик по флажку Автоматически при отправке формы
data-size normal или compact invisible
Всплывающее задание Возможно всегда Только для подозрительных сессий
Элемент в DOM div с классом .g-recaptcha div, кнопка или программный вызов

Практический вывод: у обычной v2 точка входа очевидна, а в invisible-варианте её ещё нужно найти — половина работы приходится на обнаружение, а не на решение.


Решение через API CaptchaAI: ключевой флаг invisible=1

  • Задача уходит на in.php, результат забирается опросом res.php — схема та же, что и для остальных типов reCAPTCHA.
  • Отличие от обычной v2 ровно одно: параметр invisible=1 в запросе.
  • Время решения этого типа укладывается в 30 с, поэтому опрос раз в 5 секунд с потолком около двух минут закрывает почти все случаи.

Python

import requests
import time

API_KEY = "YOUR_API_KEY"

# Submit with invisible flag
resp = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": "6Le-SITEKEY",
    "pageurl": "https://staging.example.com/qa-login",
    "invisible": "1",  # critical for Invisible reCAPTCHA
    "json": "1",
}).json()

if resp["status"] != 1:
    raise Exception(f"Submit error: {resp['request']}")

task_id = resp["request"]
print(f"Submitted: {task_id}")

# Poll for result
for _ in range(24):
    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["status"] == 1:
        print(f"Token: {result['request'][:50]}...")
        break
    if result["request"] != "CAPCHA_NOT_READY":
        raise Exception(f"Error: {result['request']}")

JavaScript

const axios = require('axios');

const resp = await axios.post('https://ocr.captchaai.com/in.php', null, {
  params: {
    key: 'YOUR_API_KEY',
    method: 'userrecaptcha',
    googlekey: '6Le-SITEKEY',
    pageurl: 'https://staging.example.com/qa-login',
    invisible: 1,
    json: 1,
  }
});
const taskId = resp.data.request;
console.log(`Submitted: ${taskId}`);

Куда подставить токен и как запустить callback

  1. Положите токен в скрытое поле g-recaptcha-response.
  2. Вызовите функцию из атрибута data-callback: invisible-виджет привязан к кнопке или к отправке формы, и без этого вызова сайт не считает значение подтверждённым.
  3. Отправьте форму штатным способом.
from selenium import webdriver
from selenium.webdriver.common.by import By

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

# After solving, inject and trigger
driver.execute_script("""
    // Set the token
    document.querySelector('textarea[name="g-recaptcha-response"]').value = arguments[0];

    // Find and trigger the callback
    var widget = document.querySelector('.g-recaptcha');
    var callbackName = widget ? widget.getAttribute('data-callback') : null;

    if (callbackName && typeof window[callbackName] === 'function') {
        window[callbackName](arguments[0]);
    }
""", token)

# Submit the form
driver.find_element(By.CSS_SELECTOR, "form#login").submit()

Когда data-callback не задан, достаточно отправить форму штатным способом: обработчик сайта прочитает поле сам.


Как найти элемент-триггер

Вариант 1: класс висит прямо на кнопке

<button class="g-recaptcha"
        data-sitekey="6Le-SITEKEY"
        data-callback="onSubmit"
        data-size="invisible">
  Submit
</button>

Вариант 2: вызов идёт из кода страницы по клику

// Site's code
document.getElementById('submit-btn').addEventListener('click', function() {
  grecaptcha.execute();
});

Проверять стоит оба шаблона: на многошаговых формах они нередко соседствуют, и триггером оказывается не та кнопка, по которой кликает тест.

// Find elements with g-recaptcha class that are buttons
document.querySelectorAll('button.g-recaptcha, input.g-recaptcha').forEach(el => {
  console.log('Trigger element:', el.tagName, el.textContent.trim());
  console.log('  data-sitekey:', el.getAttribute('data-sitekey'));
  console.log('  data-callback:', el.getAttribute('data-callback'));
});

Сценарий: ночной регресс на staging

Ночной прогон из сорока сценариев идёт в headless-режиме на CI, стенды подняты на европейском хостинге. После включения invisible-виджета на препроде половина сценариев начала падать на отправке формы: внятной ошибки в логах не было, тест видел лишь редирект на страницу входа. Разбор занял четверть часа и свёлся к трём находкам:

  • в консоли нашёлся .grecaptcha-badge;
  • класс g-recaptcha висел на кнопке «Войти», а не на отдельном div;
  • после добавления invisible=1 и явного вызова data-callback шаг стал проходить стабильно.

Отдельный момент для распределённых команд: на стендах собирайте только те данные, которые вы вправе обрабатывать — для РФ это требование 152-ФЗ «О персональных данных», для трансграничных команд обычная GDPR-дисциплина. Синтетические учётные записи снимают вопрос целиком.


Сколько потоков заложить под прогон

Тарификация у CaptchaAI идёт по потокам, а не по количеству решений: один поток — одна задача в работе, число решений внутри месяца не ограничено. Считайте не суточный объём, а параллельность прогона.

  • BASIC ($15/мес, 5 потоков) — набор smoke-тестов и отладка интеграции.
  • STANDARD ($30/мес, 15 потоков) — ночной регресс на нескольких параллельных воркерах.
  • ADVANCE ($90/мес, 50 потоков) — постоянные прогоны на нескольких стендах и в нескольких командах.

Ежемесячный платёж в USD предсказуем и удобен командам, которые ведут бюджет в другой валюте.


Диагностика: почему токен не принимается

Симптом Причина Что сделать
Сайт отклоняет внешне валидный токен В запросе нет invisible=1 Добавьте invisible: "1" в вызов CaptchaAI
Ключ сайта не находится В разметке нет блока .g-recaptcha Ищите grecaptcha.render() и grecaptcha.execute() в скриптах
Форма отправляется, но проверка не пройдена Не вызван data-callback Возьмите имя функции из атрибута и вызовите её с токеном
CAPTCHA не видна в тестах Задание показывается только подозрительным сессиям Проверьте .grecaptcha-badge и теги скрипта reCAPTCHA
Токен устарел к моменту отправки Между решением и сабмитом прошло больше двух минут Решайте задачу непосредственно перед отправкой формы

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

Как отличить Invisible v2 от reCAPTCHA v3?

  • Invisible v2 вызывает grecaptcha.execute() без параметра action, а v3 всегда передаёт действие: grecaptcha.execute(sitekey, {action: 'submit'}).
  • В URL библиотеки у v3 стоит render=SITEKEY, у invisible-варианта v2 — нет.

Где взять sitekey, если блока .g-recaptcha на странице нет?

Ключ всё равно присутствует в коде — искать его нужно в двух местах:

  • в аргументах grecaptcha.execute() или grecaptcha.render() в инлайновом скрипте (способ 3 выше);
  • в атрибуте data-sitekey на самой кнопке отправки.

Ориентир при поиске по исходному коду: ключ reCAPTCHA, как правило, начинается с 6L.

Нужен ли браузер, чтобы решить invisible-капчу?

Для самого решения — нет: API достаточно ключа сайта и адреса страницы. Браузер нужен на следующем шаге, когда токен подставляется в форму и запускается callback.

Значок reCAPTCHA скрыт стилями — это мешает решению?

Нет. Скрытый значок мешает только визуальной проверке страницы: для API важны ключ сайта, адрес страницы и флаг invisible=1. Искать виджет придётся способами 1, 3 и 4.

Какой тайм-аут поставить на ожидание результата?

Разумный потолок — около 120 с при опросе раз в 5 секунд: время решения этого типа укладывается в 30 с. Если приходит CAPCHA_NOT_READY, дождитесь результата, а не отправляйте задачу повторно — дубль займёт ещё один поток.


Подключите решение invisible-капчи к своему пайплайну

Получите API-ключ на captchaai.com.


Связанные материалы

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