Эта статья была полезной?
Как решить 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
<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
<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
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
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
document.querySelector('input[name="cf-turnstile-response"]').value = "ПОЛУЧЕННЫЙ_ТОКЕН";
Никаких функций вызывать не надо: у этого виджета нет callback, и глобальной функции сайт не ждёт. После записи остаётся заполнить форму и нажать Search.
Результат записи стоит проверить. Если виджет ещё не отработал, поля на странице нет, и обращение к нему даст ошибку. Скрипт, в котором эту ошибку заглушили, отправит форму с пустым полем и сообщит, что всё прошло успешно.
javascript
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, а эта страница открывается после выбора способа доступа и согласия с условиями |
Полный список кодов ошибок находится на странице «Коды ошибок», ссылка на неё ниже.
Полезные ссылки
- Документация API RuCaptcha по Cloudflare Turnstile: https://rucaptcha.com/api-docs/cloudflare-turnstile
- Сервис обхода Cloudflare Turnstile captcha: https://rucaptcha.com/p/cloudflare-turnstile
- Песочница для тестирования API: https://rucaptcha.com/setting#sandbox
- Коды ошибок: https://rucaptcha.com/api-docs/error-codes
- Python SDK на GitHub: https://github.com/2captcha/2captcha-python