Tutorials

Извлечение sitekey Cloudflare Turnstile: три рабочих способа

Чтобы решить Cloudflare Turnstile через API CaptchaAI, нужно всего два параметра — sitekey и pageurl. Проблема не в самом решении, а в том, что sitekey редко лежит на поверхности: один сайт отдаёт его прямо в HTML-атрибуте, другой рендерит виджет через JavaScript, третий монтирует его так, что в итоговой разметке остаётся только iframe.

Каждый из трёх случаев требует своего способа извлечения, и заранее не всегда понятно, какой именно сработает на конкретной странице. Ниже разобраны все три — от самого дешёвого DOM-запроса до перехвата вызова turnstile.render() — и рабочий код отправки задачи в CaptchaAI на Python и JavaScript, чтобы не переключаться между источниками.

Что понадобится для решения через CaptchaAI:

  • sitekey — идентификатор виджета Cloudflare Turnstile (как его найти — ниже)
  • pageurl — полный URL страницы, на которой смонтирован виджет
  • API-ключ CaptchaAI — берётся в личном кабинете на captchaai.com

Где искать sitekey Cloudflare Turnstile

Sitekey встречается в трёх местах разметки:

  1. Атрибут data-sitekey на элементах .cf-turnstile
  2. Вызов turnstile.render() в JavaScript
  3. URL iframe самого виджета

Начинайте с первого способа — он не требует запуска JavaScript и работает даже без headless-браузера. Если атрибута нет в статическом HTML, переходите ко второму и третьему по очереди: практика показывает, что хотя бы один из них сработает на любой странице с Cloudflare Turnstile.


Пример: QA-стенд с Cloudflare Turnstile перед логином

Частый сценарий для команд, тестирующих staging-окружения перед деплоем: форма логина закрыта Cloudflare Turnstile, а review-окружение поднимается заново под каждый pull request, поэтому сборка виджета от прогона к прогону немного отличается — то статическим атрибутом, то вызовом turnstile.render(). Для CI-пайплайна, который прогоняет автотесты в headless-режиме, удобнее не гадать заранее, каким способом смонтирован виджет на конкретном билде, а проверять все три источника по очереди и брать первый непустой результат. Тот же подход годится и для мониторинга изменений на партнёрских порталах, где вёрстка формы меняется без предупреждения.


Способ 1: атрибут data-sitekey в DOM

Это самый дешёвый вариант: если разметка не требует выполнения JavaScript, sitekey уже лежит в HTML-атрибуте. Проверить это можно прямо в консоли браузера — откройте DevTools на целевой странице и выполните:

// Browser console
document.querySelectorAll('.cf-turnstile').forEach((el, i) => {
  console.log(`Turnstile ${i}:`, {
    sitekey: el.getAttribute('data-sitekey'),
    action: el.getAttribute('data-action'),
    cData: el.getAttribute('data-cdata'),
    theme: el.getAttribute('data-theme'),
  });
});

Python: статический HTML

Если сервер отдаёт готовый HTML без клиентского рендеринга, хватает обычного запроса и регулярного выражения — без Selenium и без headless-браузера:

import re
import requests

html = requests.get("https://staging.example.com/qa-login").text

matches = re.findall(
    r'class=["\'][^"\']*cf-turnstile[^"\']*["\'][^>]*data-sitekey=["\']([^"\']+)',
    html
)
for sk in matches:
    print(f"Sitekey: {sk}")

Python: Selenium

Если разметка формируется динамически на клиенте, но атрибут data-sitekey всё равно попадает в итоговый DOM после рендера, обычного запроса недостаточно — нужен браузер:

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

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

widgets = driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile")
for w in widgets:
    sitekey = w.get_attribute("data-sitekey")
    action = w.get_attribute("data-action")
    print(f"Sitekey: {sitekey}, Action: {action}")

Способ 2: вызов turnstile.render в JavaScript

Часть сайтов монтирует Cloudflare Turnstile программно, без атрибута data-sitekey в исходном HTML — виджет создаётся вызовом turnstile.render() уже после загрузки скрипта:

turnstile.render('#captcha-container', {
  sitekey: '0x4AAAAAAAB...',
  callback: function(token) {
    document.getElementById('cf-token').value = token;
  },
});

В этом случае sitekey можно вытащить из исходного кода страницы или собранного JS-бандла тем же регулярным выражением, что и для DOM-атрибута — только паттерн ищет не HTML, а конфигурацию вызова:

# Find turnstile.render calls
render_match = re.search(
    r'turnstile\.render\s*\([^,]*,\s*\{([^}]+)\}',
    html
)
if render_match:
    config = render_match.group(1)
    sk = re.search(r'sitekey\s*:\s*["\']([^"\']+)', config)
    if sk:
        print(f"Sitekey from render: {sk.group(1)}")

Puppeteer: перехват turnstile.render

Регулярное выражение не всегда находит вызов — конфигурация может собираться динамически из нескольких переменных. Тогда надёжнее перехватить сам вызов turnstile.render() до того, как страница успеет его выполнить, подменив геттер/сеттер объекта window.turnstile:

// Intercept turnstile.render before page loads
await page.evaluateOnNewDocument(() => {
  window.__turnstileParams = [];
  const origRender = window.turnstile?.render;
  Object.defineProperty(window, 'turnstile', {
    set(val) {
      this._turnstile = val;
      const orig = val.render;
      val.render = function(container, params) {
        window.__turnstileParams.push(params);
        console.log('Turnstile render:', JSON.stringify(params));
        return orig.apply(this, arguments);
      };
    },
    get() { return this._turnstile; }
  });
});

await page.goto('https://staging.example.com/qa-login', { waitUntil: 'networkidle2' });

const params = await page.evaluate(() => window.__turnstileParams);
console.log('Captured Turnstile params:', params);

Способ 3: sitekey в src iframe

Какой бы способ монтирования ни использовал сайт, после отрисовки виджет всегда превращается в iframe, а sitekey остаётся в его src параметром k:

document.querySelectorAll('iframe').forEach(iframe => {
  if (iframe.src.includes('challenges.cloudflare.com')) {
    console.log('Turnstile iframe:', iframe.src);
    const match = iframe.src.match(/sitekey=([A-Za-z0-9_-]+)/);
    if (match) console.log('Sitekey:', match[1]);
  }
});

Это самый надёжный запасной вариант: даже если атрибут скрыт, а перехватить turnstile.render() не удалось, к моменту рендера виджета iframe уже гарантированно присутствует на странице.


Решение Cloudflare Turnstile через API CaptchaAI

Как только sitekey и pageurl получены любым из трёх способов выше, задача отправляется в CaptchaAI с параметром method=turnstile. Дальше — обычный цикл: отправка на in.php, затем опрос res.php до тех пор, пока статус не сменится на готовый результат:

Python

Отправка задачи и опрос результата занимают около 20 строк:

import requests
import time

API_KEY = "YOUR_API_KEY"
SITEKEY = "0x4AAAAAAAB..."
PAGE_URL = "https://staging.example.com/qa-login"

# Submit
resp = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "turnstile",
    "sitekey": SITEKEY,
    "pageurl": PAGE_URL,
    "json": "1",
}).json()

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

task_id = resp["request"]

# Poll
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:
        token = result["request"]
        print(f"Turnstile token: {token[:50]}...")
        break
    if result["request"] != "CAPCHA_NOT_READY":
        raise Exception(f"Error: {result['request']}")

JavaScript

Тот же цикл на axios, если бэкенд написан на Node.js:

const axios = require('axios');

const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
  params: {
    key: 'YOUR_API_KEY',
    method: 'turnstile',
    sitekey: '0x4AAAAAAAB...',
    pageurl: 'https://staging.example.com/qa-login',
    json: 1,
  }
});
const taskId = submit.data.request;

// Poll for result
let token = null;
for (let i = 0; i < 24; i++) {
  await new Promise(r => setTimeout(r, 5000));
  const poll = await axios.get('https://ocr.captchaai.com/res.php', {
    params: { key: 'YOUR_API_KEY', action: 'get', id: taskId, json: 1 }
  });
  if (poll.data.status === 1) {
    token = poll.data.request;
    break;
  }
}
console.log(`Token: ${token.substring(0, 50)}...`);

Передача токена в форму

Cloudflare Turnstile кладёт результат решения в скрытое поле cf-turnstile-response — именно это поле сервер проверяет при отправке формы. Полученный от CaptchaAI токен нужно вставить туда же вручную и, если у виджета настроен колбэк через data-callback, вызвать его тоже — иначе форма не узнает, что проверка пройдена:

# Selenium
driver.execute_script("""
    const input = document.querySelector('input[name="cf-turnstile-response"]');
    if (input) input.value = arguments[0];

    // Also set in the Turnstile widget's callback
    const widget = document.querySelector('.cf-turnstile');
    const callbackName = widget?.getAttribute('data-callback');
    if (callbackName && typeof window[callbackName] === 'function') {
        window[callbackName](arguments[0]);
    }
""", token)

Типичные проблемы при извлечении sitekey

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

Проблема Причина Решение
.cf-turnstile не найден Монтируется динамически Дождитесь загрузки или используйте MutationObserver
data-sitekey пустой Ключ только в JS Ищите turnstile.render (способ 2)
Токен отклонён CaptchaAI Sitekey/pageurl не совпадают с целевой страницей Проверьте оба значения ещё раз
Ошибка в method Указан userrecaptcha Передавайте method=turnstile

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

Где чаще всего прячут sitekey, если его нет в HTML?

Чаще всего — в JavaScript-бандле, в вызове turnstile.render(). Второй по частоте случай: sitekey нигде явно не указан, а виджет монтируется по data-callback, и ключ приходится доставать из src уже отрисованного iframe (способ 3).

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

Не всегда. Если атрибут data-sitekey есть в статическом HTML, хватает обычного HTTP-запроса и regex (способ 1). Headless-браузер нужен только тогда, когда виджет рендерится динамически через JavaScript и sitekey в исходной разметке не появляется.

Sitekey отличается на разных страницах одного сайта — это нормально?

Да, это встречается, если под разные формы (логин, регистрация, чекаут) настроены отдельные виджеты Cloudflare Turnstile. Извлекайте sitekey отдельно для каждой страницы: ключ с чужой страницы CaptchaAI не примет как валидный для решения текущей формы.

Turnstile сложнее решить через API, чем reCAPTCHA v2?

Нет — сложность самой отправки задачи одинаковая, разница только в значении параметра method. CaptchaAI обрабатывает оба типа, а Cloudflare Turnstile обычно решается за 10–25 секунд, что сопоставимо по времени с reCAPTCHA v2.

Есть ли у Cloudflare Turnstile невидимый режим?

Да, «managed» и «non-interactive» режимы не показывают видимый виджет пользователю. На извлечение sitekey это не влияет — ключ всё равно присутствует либо в разметке, либо в вызове turnstile.render, и все три способа из этого руководства работают одинаково.


Итоги

Проверяйте способы по порядку: DOM-атрибут → вызов turnstile.render() → src iframe. Хотя бы один из трёх сработает на любой странице с Cloudflare Turnstile, а полученные sitekey и pageurl напрямую подставляются в запрос к CaptchaAI без дополнительной обработки.

Решите Cloudflare Turnstile с CaptchaAI

Получите API-ключ на captchaai.com и подключите решение Cloudflare Turnstile к своему пайплайну по коду выше.


Связанные руководства

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