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

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

Как решить Turnstile Standalone на civitekflorida.com

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

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

Сайт поиска судебных записей Флориды (civitekflorida.com) публикует записи по трём десяткам округов. Форму поиска защищает Cloudflare Turnstile в режиме standalone: на странице выбора округа /ocrs/county/ капчи нет, виджет появляется только на странице поиска, куда вы попадаете после выбора округа, способа доступа и согласия с условиями.

Виджет создается вызовом turnstile.render в коде страницы. Из этого вызова в задачу идут два значения: action и cData. Значение cData сайт выдаёт новое при каждой загрузке страницы, так что скопировать его один раз и пользоваться им дальше не выйдет.

Готовый токен возвращается на страницу через скрытое поле cf-turnstile-response.

Параметры задачи

Задача создаётся запросом createTask. Внутри него передаётся объект task с такими параметрами:

Параметр Тип Обязательный Описание
type String Да Тип задачи: TurnstileTaskProxyless или TurnstileTask
websiteURL String Да Полный URL-адрес целевой веб-страницы, на которую загружается капча. Для этого сайта — https://www.civitekflorida.com/ocrs/app/search.xhtml
websiteKey String Да Turnstile sitekey. На этой странице это параметр sitekey вызова turnstile.render
action String Нет* Значение параметра action вызова turnstile.render. На этой странице — Search
data String Нет* Значение параметра cData вызова turnstile.render. Меняется при каждой загрузке страницы
pagedata String Нет* Значение параметра chlPageData вызова turnstile.render

Звёздочкой отмечены три параметра, которые документация требует только для Cloudflare Challenge page. Этот сайт — не Challenge page, но action и cData он виджету всё равно передаёт, поэтому оба значения в задаче нужны. А chlPageData страница не передаёт, так что поля pagedata в задаче быть не должно.

По умолчанию сервис решает капчу со своих адресов. Если решение должно приходить с вашего IP-адреса, возьмите тип задачи TurnstileTask и добавьте данные прокси: proxyType, proxyAddress, proxyPort, proxyLogin и proxyPassword.

Как найти параметры

От адреса выбора округа до капчи четыре шага.

  • Откройте https://www.civitekflorida.com/ocrs/county/, выберите округ в списке и нажмите Go. Вы попадёте на страницу округа вида /ocrs/county/02/; этот адрес можно открыть и напрямую.
  • Выберите способ доступа: Public, Attorney, Registered User или Party Access.
  • Прочитайте условия и нажмите I Agree.
  • Форма поиска откроется по адресу https://www.civitekflorida.com/ocrs/app/search.xhtml. Этот адрес и передаётся в websiteURL.

Нажмите на этой странице F12 и откройте код. Виджет подключается скриптом Cloudflare, а параметры для него задаёт небольшая функция в коде самой страницы:

javascript Copy
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback" defer></script>

window.onloadTurnstileCallback = function () {
      turnstile.render("#cfWidget", {
            //sitekey: '0x4AAAAAABDrpXgwnVQ9WVDG', // local
            sitekey: '0x4AAAAAAAR0Af-5MfzdbO3p', // non-local
            theme: 'light',
            action: 'Search',
            cData: "j1mEatXleYjrCUYdYqTwJeJ8X3i8Y1tmW-882QnqRbW4xqAiLrXSiw"
      });
};

В этом фрагменте есть три вещи, из-за которых легко ошибиться.

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

Значение cData своё у каждой загрузки страницы. Две загрузки подряд дали два разных значения. Берите cData из той же загрузки, из которой создаёте задачу, и не обновляйте страницу, пока ждёте ответ сервиса.

Поле для токена появляется вместе с виджетом. Пока render не отработал, блок #cfWidget пустой. После вызова внутри него оказывается скрытое поле cf-turnstile-response, а сам блок стоит внутри формы, которую отправляет кнопка Search. Идентификатор у поля каждый раз новый, поэтому ищите его по имени, а не по id:

javascript Copy
<div id="cfWidget" aria-hidden="true">
    <div><div></div>
        <input type="hidden" name="cf-turnstile-response" id="cf-chl-widget-znq45_response">
    </div>
</div>

Примеры кода

Python + requests

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

python Copy
import time

import requests

API_KEY = 'ВАШ_API_КЛЮЧ'

task = {
    "type": "TurnstileTaskProxyless",
    "websiteURL": "https://www.civitekflorida.com/ocrs/app/search.xhtml",
    "websiteKey": "0x4AAAAAAAR0Af-5MfzdbO3p",
    "action": "Search",
    "data": "ПОДСТАВЬТЕ_ТЕКУЩЕЕ_ЗНАЧЕНИЕ_CDATA",
}

created = requests.post(
    'https://api.rucaptcha.com/createTask',
    json={"clientKey": API_KEY, "task": task},
    timeout=30,
).json()

if created.get("errorId") != 0:
    raise SystemExit(f"Ошибка createTask: {created.get('errorDescription')}")

task_id = created["taskId"]
print(f"Задача создана, id {task_id}")

for _ in range(24):
    time.sleep(5)

    result = requests.post(
        'https://api.rucaptcha.com/getTaskResult',
        json={"clientKey": API_KEY, "taskId": task_id},
        timeout=30,
    ).json()

    if result.get("errorId") != 0:
        raise SystemExit(f"Ошибка getTaskResult: {result.get('errorDescription')}")

    if result.get("status") == "ready":
        print("Токен:", result["solution"]["token"])
        break
else:
    raise SystemExit("Задача не была решена за отведённое время")

Python + Playwright

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

python Copy
import json
import os
import re
import time

import requests
from playwright.sync_api import sync_playwright

API_KEY = os.getenv('APIKEY_RUCAPTCHA', 'ВАШ_API_КЛЮЧ')
COUNTY_CODE = '02'  # Baker County, свой код возьмите в списке округов
LAST_NAME = 'SMITH'

CREATE_TASK_URL = 'https://api.rucaptcha.com/createTask'
GET_RESULT_URL = 'https://api.rucaptcha.com/getTaskResult'


def extract_turnstile_params(html, page_url):
    # В коде страницы есть второе значение sitekey в закомментированной строке,
    # поэтому строки с комментариями отбрасываем до поиска.
    source = "\n".join(
        line for line in html.splitlines() if not line.strip().startswith("//")
    )

    def value_of(name):
        match = re.search(name + r"""\s*:\s*['"]([^'"]+)['"]""", source)
        return match.group(1) if match else None

    sitekey = value_of("sitekey")
    cdata = value_of("cData")
    action = value_of("action")

    if not sitekey or not cdata:
        raise RuntimeError("Параметры Turnstile в коде страницы не найдены")

    return {
        "type": "TurnstileTaskProxyless",
        "websiteURL": page_url,
        "websiteKey": sitekey,
        "action": action,
        "data": cdata,
    }


def solve(task):
    created = requests.post(
        CREATE_TASK_URL,
        json={"clientKey": API_KEY, "task": task},
        timeout=30,
    ).json()

    if created.get("errorId") != 0:
        raise RuntimeError(f"Ошибка createTask: {created.get('errorDescription')}")

    task_id = created["taskId"]
    print(f"Задача создана, id {task_id}")

    for _ in range(24):
        time.sleep(5)

        result = requests.post(
            GET_RESULT_URL,
            json={"clientKey": API_KEY, "taskId": task_id},
            timeout=30,
        ).json()

        if result.get("errorId") != 0:
            raise RuntimeError(f"Ошибка getTaskResult: {result.get('errorDescription')}")

        if result.get("status") == "ready":
            return task_id, result["solution"]["token"]

    raise RuntimeError("Задача не была решена за отведённое время")


def report(task_id, accepted):
    endpoint = "reportCorrect" if accepted else "reportIncorrect"
    requests.post(
        f"https://api.rucaptcha.com/{endpoint}",
        json={"clientKey": API_KEY, "taskId": task_id},
        timeout=30,
    )


def main():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()

        try:
            # 1. Доходим до формы поиска: страница округа, способ доступа, условия
            page.goto(f"https://www.civitekflorida.com/ocrs/county/{COUNTY_CODE}/")
            page.get_by_role("button", name="Public").click()
            page.get_by_role("button", name="I Agree").click()

            # 2. Ждём виджет: это поле создаёт сам Turnstile
            page.wait_for_selector('input[name="cf-turnstile-response"]')

            task = extract_turnstile_params(page.content(), page.url)
            print("Параметры со страницы:", json.dumps(task, indent=2))

            # 3. Отправляем задачу и ждём токен
            task_id, token = solve(task)
            print(f"Токен получен: {token[:40]}...")

            # 4. Записываем токен в поле ответа
            page.evaluate(
                """(token) => {
                    const field = document.querySelector('input[name="cf-turnstile-response"]');
                    if (!field) {
                        throw new Error('Поля cf-turnstile-response на странице нет');
                    }
                    field.value = token;
                }""",
                token,
            )

            # 5. Заполняем форму и отправляем её
            page.fill('input[id$=":lastname"]', LAST_NAME)
            page.get_by_role("button", name="Search", exact=True).click()
            page.wait_for_load_state("networkidle")
            page.screenshot(path="ocrs_result.png", full_page=True)

            # 6. Сообщаем сервису, принял ли сайт токен
            report(task_id, True)

        finally:
            browser.close()


if __name__ == "__main__":
    main()

Пара слов про селекторы. Идентификаторы вида form:j_idt543 расставляет фреймворк, и при обновлении сайта они меняются. Поэтому кнопки в примере находятся по надписи на них, а поле фамилии — по концу идентификатора.

Токен уходит в страницу отдельным аргументом функции, а не вставляется в текст скрипта. В токене Turnstile есть точки, подчёркивания и дефисы, и при сборке строки такой текст легко ломает сам скрипт.

Как использовать полученный токен

Готовый токен приходит в ответе сервиса, в solution.token. Записать его нужно в скрытое поле cf-turnstile-response на странице поиска.

javascript Copy
document.querySelector('input[name="cf-turnstile-response"]').value = "ПОЛУЧЕННЫЙ_ТОКЕН";

Никаких функций вызывать не надо: у этого виджета нет callback, и глобальной функции сайт не ждёт. После записи остаётся заполнить форму и нажать Search.

Результат записи стоит проверить. Если виджет ещё не отработал, поля на странице нет, и обращение к нему даст ошибку. Скрипт, в котором эту ошибку заглушили, отправит форму с пустым полем и сообщит, что всё прошло успешно.

javascript Copy
const field = document.querySelector('input[name="cf-turnstile-response"]');
if (!field) {
    throw new Error('Поля cf-turnstile-response на странице нет');
}
field.value = "ПОЛУЧЕННЫЙ_ТОКЕН";

Тестирование в режиме песочницы

Прежде чем встраивать вызов в скрипт, параметры удобно проверить в режиме песочницы. Капча при этом не уходит работникам сервиса, а возвращается вам.

  • Прочитайте на странице sitekey, action и cData и сразу отправьте задачу через createTask.
  • Откройте настройки песочницы, переключите роль на работника и войдите в приложение с ключом этой роли.
  • Если виджет Turnstile открылся и решается, параметры прочитаны верно. Если показывает ошибку, скорее всего устарело значение cData: прочитайте его заново и не обновляйте страницу между шагами.

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

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

Ошибка или проблема Причина Решение
API отвечает ошибкой на sitekey Значение взято из закомментированной строки в коде страницы Берите ключ из строки без комментария, на момент написания это 0x4AAAAAAAR0Af-5MfzdbO3p, а в коде отбрасывайте строки с комментариями
Токен получен, но сайт его не принимает Значение cData в задаче относилось к предыдущей загрузке страницы Читайте cData и создавайте задачу на одной загрузке и не перезагружайте страницу, пока ждёте токен
Поля cf-turnstile-response на странице нет Скрипт обратился к нему раньше, чем отработал виджет Дождитесь появления поля через page.wait_for_selector
Сессия истекает, пока решается задача На странице поиска работает монитор бездействия, который показывает предупреждение об истечении сессии Проходите весь цикл в одной сессии: путь до формы, задачу и отправку
Страница с капчей не открывается Пропущен шаг со страницей округа или с условиями Виджет есть только на /ocrs/app/search.xhtml, а эта страница открывается после выбора способа доступа и согласия с условиями

Полный список кодов ошибок находится на странице «Коды ошибок», ссылка на неё ниже.

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