Логотип «2Captcha»Перейти на главную страницу
Туториалы по обходу капчи

Эта статья была полезной?

Как обойти Cloudflare Challenge с помощью Playwright

Джерри Слиман
Джерри Слиман

Технический специалист

Cloudflare Challenge — это не капча внутри формы, а полная подмена целевой страницы. Вместо контента браузер получает интерстишиал «Verifying you are human», и только после успешной проверки Cloudflare пропускает его дальше. Для живого пользователя это чаще всего занимает пару секунд и проходит незаметно. Для Playwright-скрипта это тупик: page.goto() отрабатывает без ошибок, а в DOM — ни одного нужного селектора.

В этом руководстве разберём, как пройти Challenge из Playwright с помощью API RuCaptcha: перехватить динамические параметры виджета до его инициализации, отправить их на решение и передать полученный токен в callback-функцию страницы.

Challenge и standalone Turnstile — не одно и то же

Эти два кейса часто путают, а решаются они по-разному.

Standalone Turnstile Cloudflare Challenge
Где находится Виджет .cf-turnstile внутри формы на обычной странице Отдельная страница-интерстишиал вместо целевого контента
Параметры задачи websiteURL, websiteKey websiteURL, websiteKey + action, data, pagedata, userAgent
Где взять sitekey Атрибут data-sitekey в DOM Только перехватом вызова turnstile.render
Что делать с токеном Записать в input[name="cf-turnstile-response"] или вызвать callback Только вызвать callback

Если у вас первый случай — вам нужен туториал Как обойти Cloudflare Turnstile с помощью Playwright. Здесь речь идёт о втором.

Главное отличие в том, что на Challenge-странице параметры cData, chlPageData и action генерируются заново при каждой загрузке, живут считанные секунды и нигде не лежат в разметке — они передаются аргументом в вызов turnstile.render(). Значит, единственный способ их получить — подменить сам turnstile.render до того, как Cloudflare его вызовет.

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

  • Python 3.8+
  • Playwright с установленным Chromium
  • Аккаунт RuCaptcha и API-ключ (получить здесь)
  • URL целевой страницы, защищённой Cloudflare. Чтобы обкатать скрипт, удобно взять демо-страницу Cloudflare Challenge — примеры ниже написаны под неё
  • Прокси — если целевой сайт чувствителен к репутации IP (подробнее в шаге 4)

Шаг 1. Установка зависимостей

bash Copy
pip install playwright 2captcha-python
playwright install chromium

Шаг 2. Запуск браузера и подготовка перехвата

Перехватчик обязан оказаться в странице раньше, чем загрузится скрипт Turnstile. Это не рекомендация, а условие работоспособности: turnstile.render() вызывается один раз, и если к этому моменту вы его не подменили, перехватывать уже нечего — параметры уйдут внутрь виджета и останутся недоступны. Именно на этом ломается большинство скриптов: код внедряют через page.evaluate() после page.goto() и стабильно опаздывают.

В Playwright для этого есть штатный механизм — add_init_script(). Он выполняет ваш код в каждом новом документе до запуска любых скриптов страницы, включая скрипты Cloudflare.

Начнём с контекста браузера. На этапе отладки держите headless=False — так видно, что реально происходит со страницей.

python Copy
from playwright.sync_api import sync_playwright
 
USER_AGENT = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
              "(KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36")
 
with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    context = browser.new_context(user_agent=USER_AGENT, locale="en-US")
    page = context.new_page()

User-Agent задаём явно и запоминаем: строка, которую вы передаёте в задачу на решение, должна совпадать с той, под которой браузер открывает сайт. Cloudflare учитывает User-Agent при оценке запроса, и расхождение делает токен бесполезным.

Шаг 3. Перехват параметров Challenge

Внедряем init-скрипт. Он ждёт появления объекта window.turnstile, подменяет его метод render, забирает аргументы вызова и сохраняет callback в глобальной переменной window.tsCallback — именно этот callback позже примет токен.

python Copy
INTERCEPT_SCRIPT = """
const i = setInterval(() => {
    if (window.turnstile) {
        clearInterval(i);
        window.turnstile.render = (a, b) => {
            const params = {
                websiteKey: b.sitekey,
                websiteURL: window.location.href,
                data: b.cData,
                pagedata: b.chlPageData,
                action: b.action,
                userAgent: navigator.userAgent
            };
            window.tsCallback = b.callback;
            window.onChallengeParams(JSON.stringify(params));
            return;
        };
    }
}, 50);
"""

Оригинальный render вызывается кодом Cloudflare и получает два аргумента: селектор контейнера и объект настроек. Нас интересует второй — из него мы забираем sitekey, cData, chlPageData и action, а заодно сохраняем ссылку на callback. Виджет при этом не рендерится: подменённая функция просто выходит.

Теперь нужно передать перехваченные параметры из контекста страницы в Python. page.expose_function() пробрасывает в страницу настоящую Python-функцию, которую JS может вызвать напрямую — это надёжнее, чем разбирать сообщения консоли:

python Copy
import json
 
challenge_params = {}
 
def on_challenge_params(raw):
    challenge_params.update(json.loads(raw))
    print("Параметры перехвачены:", challenge_params["action"])
 
page.expose_function("onChallengeParams", on_challenge_params)
page.add_init_script(INTERCEPT_SCRIPT)
 
page.goto(TARGET_URL)
page.wait_for_function("() => window.tsCallback !== undefined", timeout=30000)

Порядок вызовов важен: expose_function и add_init_script идут до goto, иначе смысл теряется.

wait_for_function здесь надёжнее, чем time.sleep(): скрипт продолжится ровно в тот момент, когда Cloudflare реально вызвал render и callback стал доступен.

Важно: с этого момента и до отправки токена страницу нельзя перезагружать, а вкладку — закрывать. cData и chlPageData одноразовые и привязаны к конкретной загрузке документа. Перезагрузили — все перехваченные значения превратились в мусор, и API вернёт ERROR_CAPTCHA_UNSOLVABLE.

Шаг 4. Отправка задачи на решение

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

json Copy
{
    "clientKey": "YOUR_API_KEY",
    "task": {
        "type": "TurnstileTaskProxyless",
        "websiteURL": "https://rucaptcha.com/demo/cloudflare-turnstile-challenge",
        "websiteKey": "0x4AAAAAAAAjq6WYeRDKmebM",
        "action": "managed",
        "data": "80001aa1affffc21",
        "pagedata": "3gAFo2l...55NDFPRFE9",
        "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..."
    }
}

Есть два типа задач:

  • TurnstileTaskProxyless — решение выполняется через пул прокси сервиса. Подходит, если целевой сайт не привязывает результат к IP.
  • TurnstileTask — решение выполняется через ваш прокси. Дополнительно передаются proxyType, proxyAddress, proxyPort, proxyLogin, proxyPassword. Используйте этот вариант, если Challenge выдаётся агрессивно или сайт строго сверяет IP.
    Практическое правило: начните с proxyless. Если токен стабильно не принимается — переключайтесь на TurnstileTask и передавайте тот же прокси, через который работает сам Playwright.

Через официальный SDK создание задачи и опрос результата укладываются в один вызов:

python Copy
from twocaptcha import TwoCaptcha
 
solver = TwoCaptcha("YOUR_API_KEY", server="rucaptcha.com")
 
result = solver.turnstile(
    sitekey=challenge_params["websiteKey"],
    url=challenge_params["websiteURL"],
    action=challenge_params["action"],
    data=challenge_params["data"],
    pagedata=challenge_params["pagedata"],
    useragent=challenge_params["userAgent"],
)
 
token = result["code"]

Два момента, на которых легко потерять час отладки:

  • server="rucaptcha.com" обязателен. По умолчанию конструктор TwoCaptcha шлёт запросы на 2captcha.com, а аккаунты RuCaptcha и 2Captcha не общие — без этого параметра вы получите ошибку ключа.
  • Параметр называется useragent, одним словом — не user_agent. Написание важно: дополнительные аргументы уходят в API как есть, поэтому опечатка не вызовет ошибку. User-Agent просто не дойдёт до решателя, и токен окажется нерабочим.
    Решение занимает обычно 10–25 секунд.

Шаг 5. Внедрение токена через callback

Здесь Challenge принципиально отличается от standalone-виджета. Записать токен в скрытый input недостаточно и, как правило, просто бессмысленно: на интерстишиале нет формы, которую вы отправляете. Значение токена должен получить именно callback — он передаёт результат проверки Cloudflare, после чего браузер попадает на исходную страницу.

Мы сохранили этот callback в window.tsCallback ещё на шаге 3, поэтому вызов сводится к одной строке:

python Copy
page.evaluate("(token) => window.tsCallback(token)", token)

Дальше остаётся дождаться, пока Cloudflare отпустит браузер. Ориентируйтесь не на таймаут, а на появление целевого контента:

python Copy
page.wait_for_load_state("networkidle")
page.wait_for_selector("#content", timeout=30000)

Селектор подставьте свой — любой элемент, которого на странице проверки заведомо нет.

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

python Copy
import json
from playwright.sync_api import sync_playwright
from twocaptcha import TwoCaptcha
 
API_KEY = "YOUR_API_KEY"
TARGET_URL = "https://rucaptcha.com/demo/cloudflare-turnstile-challenge"
USER_AGENT = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
              "(KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36")
 
INTERCEPT_SCRIPT = """
const i = setInterval(() => {
    if (window.turnstile) {
        clearInterval(i);
        window.turnstile.render = (a, b) => {
            const params = {
                websiteKey: b.sitekey,
                websiteURL: window.location.href,
                data: b.cData,
                pagedata: b.chlPageData,
                action: b.action,
                userAgent: navigator.userAgent
            };
            window.tsCallback = b.callback;
            window.onChallengeParams(JSON.stringify(params));
            return;
        };
    }
}, 50);
"""
 
solver = TwoCaptcha(API_KEY, server="rucaptcha.com")
params = {}
 
with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    context = browser.new_context(user_agent=USER_AGENT, locale="en-US")
    page = context.new_page()
 
    page.expose_function("onChallengeParams", lambda raw: params.update(json.loads(raw)))
    page.add_init_script(INTERCEPT_SCRIPT)
 
    page.goto(TARGET_URL)
    page.wait_for_function("() => window.tsCallback !== undefined", timeout=30000)
    print("Параметры перехвачены:", params.get("action"))
 
    result = solver.turnstile(
        sitekey=params["websiteKey"],
        url=params["websiteURL"],
        action=params.get("action"),
        data=params.get("data"),
        pagedata=params.get("pagedata"),
        useragent=params["userAgent"],
    )
    token = result["code"]
    print("Токен получен")
 
    page.evaluate("(token) => window.tsCallback(token)", token)
    page.wait_for_load_state("networkidle")
    print("Текущий URL:", page.url)
 
    browser.close()

Отчёты о точности решения

Если сайт отклонил токен, отправьте reportIncorrect — это возвращает средства за неудачную попытку и помогает сервису улучшать алгоритмы. При успешном прохождении отправляйте reportCorrect.

python Copy
import requests
 
API_KEY = "YOUR_API_KEY"
TASK_ID = "123456789"
 
requests.post("https://api.rucaptcha.com/reportIncorrect",
              json={"clientKey": API_KEY, "taskId": TASK_ID})
 
requests.post("https://api.rucaptcha.com/reportCorrect",
              json={"clientKey": API_KEY, "taskId": TASK_ID})

Подробный разбор с примерами для разных языков, лимитами и разбором ошибок ERROR_REPORT_NOT_RECORDED и ERROR_DUPLICATE_REPORT — в гайде Как отправлять отчёты.

Частые ошибки и решения

Проблема Причина Решение
Параметры перехватываются пустыми Скрипт внедрён после инициализации Turnstile Используйте add_init_script() до goto(), а не evaluate() после
ERROR_CAPTCHA_UNSOLVABLE data / pagedata устарели или пустые Не перезагружайте страницу между перехватом и отправкой. Отправляйте задачу сразу после срабатывания expose_function
Токен получен, но страница не меняется Callback не вызван или вызван не тот Вызывайте именно сохранённый window.tsCallback(token)
Работает при headless=False, падает при True Cloudflare учитывает фингерпринт браузера Добавьте playwright-extra со stealth-плагином, реалистичный UA и резидентный прокси
Токен отклоняется при верном наборе параметров Расхождение User-Agent между браузером и задачей Используйте одну и ту же строку UA в new_context() и в параметре useragent
ERROR_WRONG_USER_KEY Неверный API-ключ либо не указан server="rucaptcha.com" Проверьте ключ в личном кабинете и адрес сервера в конструкторе
Ошибка поиска элементов после прохождения Сайт обновил вёрстку Актуализируйте селекторы через DevTools, используйте expect-ожидания Playwright

Полезные ссылки