Explainers

Факторы оценки reCAPTCHA: полный технический анализ

Если один и тот же скрипт получает 0,9 на рабочем ноутбуке и 0,1 в CI, дело почти никогда не в коде. Score reCAPTCHA v3 — это выход движка Advanced Risk Analysis (ARA), который сводит десятки сигналов к числу от 0,0 (почти наверняка бот) до 1,0 (почти наверняка человек), и два самых тяжёлых входа в этой сумме — класс IP-адреса и поведение в странице. Переезд с домашнего интернета в облачный регион меняет оба сразу.

Точную формулу Google не публикует, но набор входных данных известен по многолетним измерениям и разбору клиентского кода. Ниже — разбор в порядке отладки: от того, что проверяется за минуту и чаще всего оказывается причиной, к тому, что стоит трогать последним. Все числовые сдвиги — наблюдаемые ориентиры, а не константы: веса Google меняет без анонсов.


Как сигналы складываются в итоговый score

Формально итог можно записать так:

Final Score = f(Browser Signals, Behavioral Signals, Network Signals, 
                History Signals, Environmental Signals)

Ключевая деталь: сигналы не суммируются линейно. Одно жёсткое противоречие — например, SwiftShader в WebGL при заявленном настольном Chrome — обесценивает несколько «правильных» сигналов, потому что несогласованность сама по себе читается как признак подмены. Наращивать «плюсы» бессмысленно, пока в наборе остаётся хотя бы одно очевидное расхождение.

Отсюда и порядок разбора, который экономит больше всего времени:

  1. Откуда идёт запрос — класс адреса и рукопожатие TLS.
  2. Чем притворяется клиент — набор браузерных API, canvas, WebGL.
  3. Что происходило на странице — мышь, клавиатура, прокрутка.
  4. Что Google уже знает про этот профиль — cookie и история.
  5. Мелочи окружения — часовой пояс, размеры окна, батарея.

Сеть: класс IP, TLS-отпечаток и заголовки

Начинать разбор логично не с браузера, а с сети — это самый дешёвый в диагностике блок. Google ведёт базу репутации адресов и учитывает историю проверок с конкретного IP, принадлежность к диапазонам облачных провайдеров, прокси и VPN.

Тип IP Типичное влияние на оценку
Домашний, без истории нарушений Нейтрально до +0,1
Домашний, с эпизодами нарушений от −0,1 до −0,2
Дата-центр от −0,3 до −0,5
Известный прокси/VPN от −0,2 до −0,4
Выходной узел Tor от −0,4 до −0,6

Типичная история из практики: у команды в Алматы интеграционные тесты годами шли с рабочих ноутбуков и стабильно проходили, потом их перенесли в CI на европейский облачный регион — и score упал вдвое при неизменном коде. Менялся не скрипт, а класс адреса. Тот же эффект даёт переезд стенда с офисного канала на арендованный VPS.

Второй сетевой слой — рукопожатие TLS. Набор шифров, их порядок и список расширений у каждого клиента свои, и по ним отпечаток JA3/JA4 читается ещё до того, как отработает хоть одна строка JavaScript:

  • Chrome — TLS 1.3, характерный порядок шифров, расширения GREASE.
  • python requests — другой порядок, GREASE нет, список расширений короче.
  • curl — минимальный набор по шаблонам OpenSSL.

Небраузерный отпечаток стоит от −0,2 до −0,4, и подменой одного User-Agent это не лечится: заголовок и рукопожатие формируются разными слоями, и их расхождение хорошо видно на стороне сервера.

Сами заголовки проверяются на согласованность друг с другом:

Заголовок Признак человека Признак бота
User-Agent Соответствует реальной версии браузера Устаревшая версия, UA инструмента автоматизации
Accept-Language Заполнен языковыми настройками Отсутствует или слишком общий
Accept-Encoding gzip, deflate, br Отсутствует или нетипичен
Sec-CH-UA Client Hints, согласованные с браузером Отсутствуют (набор заголовков до 2022 года)

Браузерная среда: проверка на подлинность

Набор доступных JavaScript API

reCAPTCHA опрашивает API, которых в браузере в headless-режиме либо нет, либо они реализованы иначе:

Проверяемый API Ожидается в обычном браузере Признак headless-режима
navigator.webdriver undefined или false true (Selenium, Puppeteer)
navigator.plugins PluginArray с одним и более элементом Пустой или отсутствует
navigator.languages Массив строк локали Пустой массив или отсутствует
navigator.hardwareConcurrency 2–16 (соответствует CPU) Иногда 0 или undefined
navigator.deviceMemory 2–16 ГБ В части сборок отсутствует
window.chrome Объект с runtime и прочим Отсутствует в headless Chrome
Notification.permission default, granted или denied Бросает исключение или отсутствует

Влияние на оценку: пропуски и неправдоподобные значения в этом наборе стоят от 0,3 до 0,5 балла.

Отпечаток canvas

Движок отрисовывает скрытый canvas и считывает пиксели. Результат уникален для связки браузер/OS/GPU.

// reCAPTCHA performs something similar to:
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.textBaseline = "top";
ctx.font = "14px 'Arial'";
ctx.fillText("Hello", 2, 2);
const fingerprint = canvas.toDataURL();

Как это читается движком:

Результат отрисовки Сдвиг оценки
Стабильный, согласованный с заявленными браузером и OS нейтрально
Совпадает с известным шаблоном headless-сборки от −0,3 до −0,5
Операции с canvas заблокированы или дают однородные данные от −0,4 до −0,6

Обратите внимание: блокировка canvas хуже, чем «неидеальный» результат. Отсутствие сигнала — тоже сигнал, и приватные расширения, вырезающие canvas, регулярно роняют score живым пользователям.

Строка WebGL-рендерера

Строка рендерера показывает видеокарту:

const gl = document.createElement('canvas').getContext('webgl');
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
// Expected: "ANGLE (NVIDIA GeForce GTX 1060...)"
// Headless: "SwiftShader" or "Google SwiftShader"

Влияние на оценку: SwiftShader — программный рендерер, который Chrome использует в headless-режиме, — снижает score на 0,3–0,5.


Поведение в странице: блок с наибольшим весом

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

Что видно по движению мыши

reCAPTCHA пишет координаты курсора и временные метки на протяжении всего визита.

Сигнал Человек Бот
Количество событий 50–500+ за просмотр страницы 0–5 событий
Траектория Изогнутые пути с переменной скоростью Прямые линии, постоянная скорость
Ускорение Плавные разгон и торможение Мгновенные старт и стоп
Микродвижения Лёгкое дрожание при наведении Полная неподвижность между действиями
Промах по цели Небольшой перелёт с коррекцией Идеальное попадание в центр элемента

Влияние на оценку: полное отсутствие движений мыши стоит от −0,4 до −0,7 — больше, чем любой другой одиночный фактор.

Тайминги клавиатуры

Inter-key timing:
  Human: Variable (50-300ms between keys, varies by key pair)
  Bot: Constant (all keys at same interval, or instant paste)

Key events:
  Human: keydown → keypress → keyup for each character
  Bot: May skip keypress, or fire all events simultaneously

Corrections:
  Human: Occasional backspace, re-typing
  Bot: Perfect input without corrections

Влияние на оценку: мгновенная подстановка текста — через буфер обмена или присвоение value в JavaScript — снижает score на 0,1–0,2.

Прокрутка и порядок взаимодействия

Человек прокручивает страницу неравномерно, с паузами на чтение, кликает по посторонним элементам и наводит курсор на ссылки. Бот открывает страницу и сразу оказывается в полях формы.

Что фиксирует движок Сдвиг оценки
Ни одного события прокрутки перед работой с формой от −0,2 до −0,3
Переход сразу к полям без «осмотра» страницы от −0,1 до −0,3
Заполнение полей строго по порядку DOM, без возвратов от −0,1 до −0,2

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


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

Что видит движок Сдвиг оценки
Cookie SID, HSID, SSID — активная сессия Google от +0,1 до +0,3
Cookie _GRECAPTCHA — было успешное решение ранее нейтрально до +0,1
Ни одной cookie Google и reCAPTCHA от −0,1 до −0,2

Служебные rc::a, rc::b, rc::c хранят данные анализа рисков и живут вместе с профилем. Отсюда три практических правила для стендов:

  • Профиль браузера, переживающий перезапуски, работает заметно лучше одноразового контейнера — каталог профиля стоит монтировать томом, а не пересоздавать на каждый прогон.
  • Один профиль на один домен: Google сопоставляет поведение профиля между сайтами, и грубая автоматизация на одном из них тянет вниз все остальные.
  • Профиль, который месяцами использовался только для прогонов и ни разу не видел живой сессии, со временем перестаёт давать прибавку — репутация накапливается по факту нормального использования, а не по возрасту cookie.

Окружение: слабые, но заметные корректоры

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

Первый — часовой пояс:

const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
// Should match IP geolocation
// "America/New_York" with a New York IP → consistent
// "America/New_York" with a Tokyo IP → suspicious

Расхождение часового пояса с геолокацией адреса — классический побочный эффект прокси: контейнер живёт в UTC, а адрес принадлежит другому региону. Для команд, которые арендуют прокси в одной стране, а стенды держат в другой, это регулярный источник необъяснимых просадок. Лечится дёшево: задайте контейнеру TZ под регион выходного узла и держите то же значение в языковых настройках профиля.

Второй — геометрия экрана и окна:

screen.width, screen.height       // Physical screen
window.innerWidth, innerHeight    // Browser window
window.outerWidth, outerHeight    // Including browser chrome

Третий — батарея, если API доступен:

navigator.getBattery().then(battery => {
    battery.level;    // 0.0 to 1.0
    battery.charging; // true/false
});

Итог по блоку:

Сигнал Что настораживает движок Сдвиг оценки
Часовой пояс UTC при адресе из другого региона от −0,1 до −0,2
Размеры окна 0×0, чрезмерно большие или ровно дефолтные для инструмента от −0,1 до −0,2
Батарея постоянные 100 % заряда и подключение к сети почти нейтрально

Что с этим делать в автоматизации

Вариант первый: приводить среду в порядок

Мера Ожидаемый эффект Трудоёмкость
Запускать настоящий Chrome вместо headless-режима от +0,3 до +0,5 низкая
Убрать флаг navigator.webdriver от +0,2 до +0,3 низкая
Добавить имитацию движения мыши от +0,2 до +0,4 средняя
Использовать резидентный прокси от +0,2 до +0,3 зависит от бюджета
Сохранять cookie Google между запусками от +0,1 до +0,2 средняя
Согласовать TLS-отпечаток с Chrome от +0,1 до +0,3 высокая
Имитировать осмотр страницы до формы от +0,1 до +0,2 средняя

Каждая строка выполнима по отдельности. Проблема в том, что поддерживать весь набор постоянно — это отдельный продукт внутри вашего: Chrome обновляется, наборы шифров меняются, пул прокси деградирует.

Вариант второй: получать токен через API

Вместо ручной подгонки всех сигналов задачу можно отправить в CaptchaAI и забрать готовый токен. Ниже — стандартная схема «отправить задачу → опрашивать res.php» для reCAPTCHA v3 на QA-стенде:

import requests
import time

API_KEY = "YOUR_API_KEY"

# Request a high-score token
submit = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": "6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp",
    "pageurl": "https://staging.example.com/qa-login",
    "version": "v3",
    "action": "login",
    "json": 1,
})

task_id = submit.json()["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:
        token = result["request"]
        print(f"High-score token received")
        break

Полученное значение подставляется в поле g-recaptcha-response вашей формы. Параметры googlekey и pageurl берутся со страницы, где стоит проверка, а action должен совпадать с тем, что передаёт сам сайт, — иначе Google посчитает контекст несогласованным.

Тарификация здесь по потокам, а не по числу решений: план BASIC ($15/мес, 5 потоков) закрывает регрессионный прогон небольшой команды, ADVANCE ($90/мес, 50 потоков) — параллельный CI. Один поток занят ровно на время одного решения и сразу берёт следующее, поэтому считать нужно пиковую одновременность, а не суточный объём задач. Для агентств и фрилансеров, которые получают доход в валюте, отличной от USD, это удобно тем, что сумма счёта фиксирована и не зависит от того, сколько проверок отработал прогон.

Схема вызова одинакова для всех поддерживаемых типов: reCAPTCHA v2 и v3, Cloudflare Turnstile и Cloudflare Challenge, GeeTest v3, image/OCR, grid и BLS — меняются только method и параметры типа, так что один обработчик закрывает сразу несколько сценариев. hCaptcha и FunCaptcha сервис не решает, GeeTest v4 заявлен как готовящийся, а CaptchaFox, Friendly Captcha и Lemin доступны в статусе beta.

И ремарка про данные: если автоматизация попутно собирает пользовательские сведения, ограничьте выборку тем, что вы вправе обрабатывать, — требования 152-ФЗ и GDPR к минимизации данных применимы здесь так же, как везде.


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

Какой score считается нормальным?

Ориентир — 0,7 и выше: такое значение большинство внедрений пропускает без дополнительной проверки. Порог владелец сайта задаёт сам, типовой диапазон — от 0,5 до 0,7. Значения ниже 0,3 почти всегда означают дополнительную проверку или отказ.

Всегда ли VPN снижает оценку?

Не сам по себе. Значение имеет репутация конкретного адреса: выходные узлы VPN в дата-центрах теряют от 0,2 до 0,4, а адреса резидентного класса ведут себя заметно лучше. Оценивается диапазон, а не факт использования VPN.

Почему score отличается на разных страницах одного сайта?

reCAPTCHA v3 учитывает параметр action, и у разных действий (главная, вход, оформление заказа) базовый уровень свой. Плюс поведенческие данные копятся в течение сессии: на второй-третьей странице движку доступно больше материала, поэтому оценка часто выше, чем на первой.

Можно ли задать нужный score прямо в запросе к API?

Нет. Параметра вроде score-target не существует: значение назначает Google на своей стороне. Повлиять можно только косвенно — передавая в запросе cookies, userAgent и proxy, чтобы контекст решения был ближе к реальной пользовательской сессии. Для сайтов на reCAPTCHA Enterprise дополнительно указывается enterprise=1.

Что чинить первым при низком score?

Класс IP и поведение. Переход на резидентный прокси и имитация движений мыши обычно дают больший прирост, чем любая правка navigator.*. Тонкая настройка TLS-отпечатка — последний шаг, а не первый: она дорогая по трудозатратам и почти не помогает, пока запросы идут из диапазона дата-центра.


Коротко

Веса распределены неравномерно: класс адреса и поведение решают больше, чем браузерные API, cookie и окружение вместе взятые. В этом же порядке стоит вести отладку — иначе легко потратить неделю на navigator.* и не сдвинуть оценку.

Порядок действий, если score просел на стенде:

  1. Проверьте, из какого диапазона идут запросы, и при необходимости переведите прогон на резидентный прокси.
  2. Добавьте движение курсора и прокрутку перед отправкой формы.
  3. Сохраните профиль браузера между запусками.
  4. Только после этого беритесь за navigator.*, canvas и TLS-отпечаток.

Если автоматизации нужен предсказуемый результат без бесконечной поддержки браузерной обвязки, отдайте получение токена в CaptchaAI и передавайте вместе с задачей cookies, userAgent и резидентный proxy.

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

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