Как решить reCAPTCHA с параметром data-s в Python

recaptcha data-s parameter - How to Solve reCAPTCHA with the data-s Parameter in Python

На собственных страницах Google виджет reCAPTCHA несёт дополнительное значение, и называется оно data-s. Решите такую капчу обычным способом, и получите токен, который страница потом отклонит. Лечится это одним дополнительным аргументом: считайте data-s из HTML страницы, передайте решателю и сразу же отправьте токен.

Параметр recaptcha data-s упускают почти все, в основном потому, что на обычных сайтах его просто нет. Разбираем, где он лежит, как его называть и какой скрипт работает от начала до конца.

Что такое data-s на самом деле

Это короткоживущее одноразовое значение, которое Google добавляет к виджетам reCAPTCHA на своих собственных ресурсах. Чаще всего вы столкнётесь с промежуточной страницей “unusual traffic” в Google Search. Значение привязывает задание к конкретной загрузке страницы, поэтому полученный токен действителен только в том же контексте запроса.

Из этого следуют три вещи, и все три приводят к сбоям:

  • Оно истекает. Относитесь к нему как к nonce. Загрузите страницу, решите капчу, отправьте. Не кэшируйте его между запусками.
  • Оно только для Google. У reCAPTCHA на чужом сайте нет data-s. Если отправить его всё равно, это будет ошибка параметра, а не безобидное дополнение.
  • Это не флаг Enterprise. Их постоянно путают. data-s и enterprise являются отдельными опциями, решающими разные задачи, а страница Решение для reCAPTCHA Enterprise рассказывает про вторую из них.

Что понадобится

  • Запущенный локально CapSkip. По умолчанию он слушает на 127.0.0.1:8080 , а страница руководство по настройке описывает установку.
  • Python 3.10 или новее.
  • SDK и HTTP-клиент.
# the CapSkip SDK plus requests for fetching the page
pip install capskip requests

Где найти значение data-s

Оба нужных значения лежат на одном элементе. Виджет рендерится как div с атрибутом data-sitekey и, только на страницах Google, data-s:

<div class="g-recaptcha"
     data-sitekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
     data-s="SGVsbG8gdGhlcmUsIHRoaXMgaXMgYSBvbmUtdGltZSB2YWx1ZQ">
</div>

Sitekey стабилен, его можно захардкодить. А значение data-s меняется при каждой загрузке, поэтому его приходится извлекать во время выполнения:

# pip install capskip requests
import re
import requests

PAGE = "https://www.google.com/search?q=example"

# Keep one session. The value is tied to the request that
# produced it, cookies included.
session = requests.Session()
html = session.get(PAGE, timeout=30).text

sitekey = re.search(r'data-sitekey="([^"]+)"', html).group(1)
datas   = re.search(r'data-s="([^"]+)"', html).group(1)

print(sitekey, datas)   # second value is single use

Если второе совпадение вернулось пустым, значит, вы не на странице Google и этот параметр вам вообще не нужен. Решайте капчу как обычную задачу v2.

Передавайте его как datas, а не data-s

Это ловушка с именованием. Прямой HTTP API принимает параметр ровно так, как он записан в HTML, data-s. SDK не могут использовать это имя, потому что дефис недопустим в именованном аргументе Python, поэтому каждый SDK выставляет его как datas.

ГдеИмя
HTML страницыdata-s
Прямой API на in.phpdata-s
SDK для Python, Node.js, PHP, .NETdatas

С этим разобрались, и решение капчи умещается в один вызов:

from capskip import CapSkip

solver = CapSkip(host="127.0.0.1", port=8080)

result = solver.recaptcha(
    sitekey=sitekey,
    url=PAGE,
    datas=datas,      # the data-s value, scraped seconds ago
)

print(result["code"])   # g-recaptcha-response token

Полный рабочий пример

Загрузка, парсинг, решение и отправка идут в одной сессии, чтобы cookie и IP оставались одинаковыми:

# pip install capskip requests
import re
import requests
from capskip import (
    CapSkip, ApiException, NetworkException, TimeoutException,
)

PAGE = "https://www.google.com/search?q=example"

session = requests.Session()
solver = CapSkip(host="127.0.0.1", port=8080, recaptchaTimeout=300)

html = session.get(PAGE, timeout=30).text
sitekey = re.search(r'data-sitekey="([^"]+)"', html).group(1)
datas   = re.search(r'data-s="([^"]+)"', html).group(1)

try:
    result = solver.recaptcha(sitekey=sitekey, url=PAGE, datas=datas)
except NetworkException:
    raise SystemExit("CapSkip is not running on 127.0.0.1:8080")
except TimeoutException:
    raise SystemExit("solve took longer than recaptchaTimeout")
except ApiException as err:
    raise SystemExit(f"API refused the task: {err}")

# Submit immediately. The token is good for about two minutes.
response = session.post(
    PAGE,
    data={"g-recaptcha-response": result["code"]},
    timeout=30,
)
print(response.status_code)

Две детали здесь важнее, чем кажется. Объект session переиспользуется, поэтому решение и отправка ходят с одними и теми же cookie. А отправка идёт сразу после решения, потому что токен reCAPTCHA перестаёт приниматься примерно через две минуты после выдачи.

Работа через прокси

Если вы загрузили страницу через прокси, решайте капчу через него же. Задание привязано к запросу, который его создал, а решение с другого сетевого пути становится второй по частоте причиной отклонённого токена.

result = solver.recaptcha(
    sitekey=sitekey,
    url=PAGE,
    datas=datas,
    proxy={"type": "HTTPS", "uri": "user:[email protected]:3128"},
)

Прокси работают для reCAPTCHA, Turnstile и GeeTest. Для графических капч они бесполезны, ведь те вообще не обращаются к целевому сайту.

Тот же вызов без SDK

API совместим с 2captcha и работает на вашей машине, так что подойдёт любой HTTP-клиент. Здесь параметр сохраняет дефис:

# submit the task, get an ID back
curl -s -X POST "http://127.0.0.1:8080/in.php" \
  -d "key=capskip" \
  -d "method=userrecaptcha" \
  -d "googlekey=YOUR_SITEKEY" \
  -d "pageurl=https://www.google.com/search?q=example" \
  -d "data-s=THE_DATA_S_VALUE" \
  -d "json=1"

# {"status":1,"request":"2122988149"}

Затем опрашивайте res.php для получения токена. Полный поток из двух вызовов, включая задержки между опросами, разобран в статье решение капчи через cURL.

Другие SDK используют такое же имя datas для этого параметра. В Node.js его передают в объекте опций, а в .NET кладут в словарь опций:

// npm install capskip
const { CapSkip } = require('capskip');

const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });

const result = await solver.recaptcha(sitekey, pageUrl, {
  datas: dataS,        // same value, same rules
});

console.log(result.code);

Частые ошибки

Что вы видитеПричинаИсправить
ERROR_GOOGLEKEYSitekey не попал в запрос, или регулярное выражение поймало не тот атрибутВыведите извлечённое значение перед решением. Подробности есть в статье исправление ERROR_GOOGLEKEY
ERROR_BAD_PARAMETERSdata-s отправлен пустой строкой или отправлен на страницу, не принадлежащую GoogleПередавайте его только тогда, когда атрибут действительно есть на странице
ERROR_CAPTCHA_UNSOLVABLEОбычно устаревший data-s: страница была загружена за несколько минут до решенияЗагружайте страницу и решайте капчу подряд. Смотрите ERROR_CAPTCHA_UNSOLVABLE
Токен принят решателем, но отклонён GoogleРазные cookie или разный IP между загрузкой и отправкойИспользуйте одну сессию и один и тот же прокси для обоих запросов
NetworkExceptionCapSkip не запущенЗапустите приложение и проверьте порт в настройках

Все параметры и форматы ответа перечислены в Документация по API.

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

Нужен ли data-s на обычном сайте?

Нет. Он встречается только на собственных страницах Google. Если data-s в HTML нет, полностью опустите этот аргумент и решайте капчу как стандартную задачу v2, именно так, как страница Решатель reCAPTCHA v2 это и описывает.

Совпадает ли data-s с флагом Enterprise?

Нет, и они независимы друг от друга. enterprise=1 сообщает решателю, с каким продуктом reCAPTCHA он имеет дело. datas передаёт одноразовое значение со страницы. Капче в Google Search нужен второй параметр, и оба можно передать вместе, если страница действительно этого требует.

Как долго значение остаётся действительным?

Считайте, что секунды, а не минуты. Оно выдаётся вместе со страницей и умирает вместе с ней. Если ваш парсер ставит страницы в очередь и решает капчи позже, загружайте страницу заново в момент решения, а не храните значение.

Почему в коде datas, а в HTML data-s?

Потому что дефис не может стоять в именованном аргументе Python или в идентификаторе C#. SDK убирают его и сами отправляют имя с дефисом по сети. Прямой вызов in.php по-прежнему ожидает data-s.

Сводка

Извлеките data-sitekey и data-s с одного и того же элемента, второе передайте как datas, и отправьте токен в той же сессии в пределах пары минут. Пропустите любой из этих трёх шагов, и получите токен, который выглядит нормально и не работает.

Остальные возможности Python описаны на странице Решатель капчи для Python . CapSkip делает всё это как локальный обход капчи у вас на машине, поэтому загрузить страницу второй раз ради свежего значения не стоит вам ничего, кроме самого запроса.