Selenium не проходит CAPTCHA — и не должен. Проверку решает внешний сервис, а браузер остаётся тем, чем был: открывает страницу, читает DOM и отправляет форму. Скрипт достаёт sitekey из вёрстки, передаёт его вместе с URL в API CaptchaAI, получает токен и подставляет его в скрытое поле формы. По виджету при этом никто не кликает.
Ниже — рабочая схема на Python: драйвер, функция решения, извлечение параметров, вставка токена и разбор ошибок, которые чаще всего ломают такой сценарий.
Что понадобится перед стартом
| Компонент | Как поставить или получить |
|---|---|
| Python 3.7+ | вместе с pip |
| Selenium | pip install selenium |
| Chrome + ChromeDriver | совпадающие мажорные версии |
| requests | pip install requests |
| API-ключ CaptchaAI | в панели управления на captchaai.com |
Если ключ ещё не заведён, сделайте первый запрос к API вручную, без Selenium: так вы отделите проблемы интеграции от проблем браузера.
Схема работы: четыре шага вместо клика по виджету
- Selenium открывает целевую страницу.
- Скрипт извлекает
sitekeyпроверки CAPTCHA из DOM. - CaptchaAI решает задачу по паре «
sitekey+ URL страницы». - Скрипт вставляет полученный токен в форму и отправляет её.
CAPTCHA решается на стороне сервиса: Selenium не взаимодействует с виджетом, ему достаточно загрузить страницу и отдать два параметра.
Шаг 1: настройте драйвер Chrome
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
options = Options()
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
driver = webdriver.Chrome(options=options)
Аргумент --disable-blink-features=AutomationControlled убирает заметный признак автоматизации, из-за которого часть форм ведёт себя иначе даже при корректном токене. Ещё два параметра снимают служебную панель Chrome:
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
Драйвер можно запускать и в headless-режиме: для получения токена достаточно, чтобы страница отрисовалась и sitekey появился в DOM.
Шаг 2: напишите функцию решения
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_recaptcha_v2(site_key, page_url):
"""Solve reCAPTCHA v2 using CaptchaAI API."""
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url
})
if not resp.text.startswith("OK|"):
raise Exception(f"Submit failed: {resp.text}")
task_id = resp.text.split("|")[1]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY,
"action": "get",
"id": task_id
})
if result.text == "CAPCHA_NOT_READY":
continue
if result.text.startswith("OK|"):
return result.text.split("|")[1]
raise Exception(f"Solve failed: {result.text}")
raise TimeoutError("CAPTCHA solve timed out")
Что важно в этом коде:
in.phpпринимает задачу и возвращаетOK|<ID задачи>;res.phpопрашивается до готовности результата.- Ответ
CAPCHA_NOT_READY— рабочее состояние очереди. Любой другой текст без префиксаOK|— код ошибки, его лучше пробрасывать наружу, а не глотать. - Интервал 5 секунд и 60 итераций дают запас примерно на пять минут. На практике ответ приходит раньше, но при нестабильном канале запас снимает ложные тайм-ауты.
Тарификация построена на потоках, а не на количестве решений: BASIC ($15/мес, 5 потоков) — это пять одновременных задач без лимита на их число за месяц. Для прогона на 20–30 воркеров берите ADVANCE ($90/мес, 50 потоков).
Шаг 3: получите sitekey со страницы
# Navigate to the target page
driver.get("https://staging.example.com/qa-login")
# Wait for the reCAPTCHA to load
wait = WebDriverWait(driver, 10)
recaptcha = wait.until(
EC.presence_of_element_located((By.CLASS_NAME, "g-recaptcha"))
)
# Extract the site key
site_key = recaptcha.get_attribute("data-sitekey")
page_url = driver.current_url
print(f"Site key: {site_key}")
print(f"Page URL: {page_url}")
# Solve the CAPTCHA
token = solve_recaptcha_v2(site_key, page_url)
print(f"Token received: {token[:50]}...")
Две детали, на которых чаще всего спотыкаются:
WebDriverWaitздесь не формальность: если виджет подгружается скриптом после основного контента, немедленный поискg-recaptchaупадёт сNoSuchElementException.page_urlберите изdriver.current_url, а не из константы: при редиректе сервису нужен фактический адрес, на котором отрисован виджет.
Шаг 4: вставьте токен и отправьте форму
# Inject the token into the reCAPTCHA response field
driver.execute_script(f"""
document.getElementById('g-recaptcha-response').innerHTML = '{token}';
document.getElementById('g-recaptcha-response').style.display = '';
""")
# If the form uses a callback function, trigger it
driver.execute_script(f"""
if (typeof ___grecaptcha_cfg !== 'undefined') {{
Object.keys(___grecaptcha_cfg.clients).forEach(function(key) {{
var client = ___grecaptcha_cfg.clients[key];
if (client.callback) client.callback('{token}');
}});
}}
""")
# Submit the form
driver.find_element(By.CSS_SELECTOR, "form").submit()
# Wait for navigation
wait.until(EC.url_changes(page_url))
print(f"Success! Now on: {driver.current_url}")
Здесь работают два разных сценария:
- Обычная форма. Первый вызов
execute_scriptзаполняет скрытое полеg-recaptcha-responseи снимаетdisplay: none— часть форм проверяет видимость поля. - Форма с обратным вызовом. Сабмита нет, страница ждёт вызова JavaScript-функции с токеном; перебор
___grecaptcha_cfg.clientsзапускает её вручную.
Токен живёт около двух минут — всё долгое (скриншот, запись в базу) переносите после сабмита.
Полный пример: один файл, который можно запустить
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_recaptcha_v2(site_key, page_url):
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url
})
if not resp.text.startswith("OK|"):
raise Exception(f"Submit failed: {resp.text}")
task_id = resp.text.split("|")[1]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id
})
if result.text == "CAPCHA_NOT_READY":
continue
if result.text.startswith("OK|"):
return result.text.split("|")[1]
raise Exception(f"Solve failed: {result.text}")
raise TimeoutError("Timed out")
def main():
options = Options()
options.add_argument("--disable-blink-features=AutomationControlled")
driver = webdriver.Chrome(options=options)
try:
driver.get("https://staging.example.com/qa-login")
wait = WebDriverWait(driver, 10)
# Extract site key
recaptcha = wait.until(
EC.presence_of_element_located((By.CLASS_NAME, "g-recaptcha"))
)
site_key = recaptcha.get_attribute("data-sitekey")
# Solve
token = solve_recaptcha_v2(site_key, driver.current_url)
# Inject and submit
driver.execute_script(
f"document.getElementById('g-recaptcha-response').innerHTML = '{token}';"
)
driver.find_element(By.CSS_SELECTOR, "form").submit()
wait.until(EC.url_changes(driver.current_url))
print("Login successful!")
finally:
driver.quit()
if __name__ == "__main__":
main()
Блок try/finally с driver.quit() обязателен: без него упавший на середине прогон оставляет процесс chromedriver в памяти, и на CI такие «хвосты» копятся, пока раннер не начнёт падать по нехватке ресурсов.
Другие типы проверок в том же скрипте
Смена типа CAPTCHA меняет только тело запроса к in.php; опрос res.php и вставка токена остаются прежними.
reCAPTCHA v3
def solve_recaptcha_v3(site_key, page_url, action="verify"):
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url,
"version": "v3",
"action": action
})
task_id = resp.text.split("|")[1]
# ... same polling logic
У v3 нет виджета и клика: сервер оценивает поведение и возвращает score. Параметр action должен совпадать с тем, который использует страница, иначе оценка окажется низкой. Отдельного поля для токена здесь обычно нет — его подставляют в скрытый input формы.
Cloudflare Turnstile
def solve_turnstile(site_key, page_url):
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "turnstile",
"sitekey": site_key,
"pageurl": page_url
})
task_id = resp.text.split("|")[1]
# ... same polling logic
Обратите внимание на имя параметра: у Turnstile это sitekey, а не googlekey, и токен вставляется в поле cf-turnstile-response. Для GeeTest v3 логика опроса та же, но параметров в запросе больше.
Прежде чем строить сценарий, сверьтесь с матрицей типов:
- Штатно: reCAPTCHA v2 и v3 (включая Enterprise-варианты), Cloudflare Turnstile и Cloudflare Challenge, GeeTest v3, картиночные и текстовые проверки, grid-image, BLS CAPTCHA.
- В бета-статусе: CaptchaFox (beta), Friendly Captcha (beta), Lemin (beta).
- Не решается: hCaptcha и FunCaptcha (Arkose Labs). Поддержка GeeTest v4 заявлена как «скоро» и пока недоступна.
Сценарий из практики: ночной прогон регрессии
Знакомая ситуация в командах, которые деплоятся в европейские регионы или в дата-центры Казахстана: ночной прогон из 40 сценариев, у восьми на входе форма с reCAPTCHA v2. Эти восемь помечали как skip, и регрессия не покрывала авторизацию. Что меняется, когда решение проверки вынесено в API:
- Стенд, а не боевой сайт. Тесты идут по staging-домену с тем же
sitekey, что и на проде. - Параллелизм упирается в потоки. Восемь сценариев в один поток встанут в очередь; на пяти потоках BASIC они пройдут почти одновременно.
- Тайм-аут теста считайте от времени решения. При общем лимите сценария в 60 секунд и 30 секундах на ответ сервиса лимит нужно поднять — иначе тест упадёт не по логике, а по секундомеру.
- Данные под контролем. Требования 152-ФЗ «О персональных данных» действуют и в тестовом контуре, поэтому проще держать на стенде синтетические записи.
Что ломается чаще всего
| Симптом | Причина | Что делать |
|---|---|---|
| Форма отклоняет токен | срок действия токена истёк | вставляйте и отправляйте в пределах 120 с |
sitekey не найден |
виджет подгружается асинхронно | увеличьте тайм-аут WebDriverWait |
NoSuchElementException |
неверный селектор | сверьте актуальную вёрстку в DevTools |
| Ошибка версии ChromeDriver | Chrome обновился | поставьте драйвер под текущий мажорный релиз |
ERROR_ZERO_BALANCE в ответе res.php |
закончился оплаченный период | проверьте баланс в панели управления |
| Токен принят, но форма не уходит | форма ждёт обратного вызова | вызовите callback через execute_script |
| Проверка пройдена, доступ всё равно закрыт | на сайте есть защита помимо CAPTCHA | это отдельный слой, токен на него не влияет |
Часто задаваемые вопросы
Сколько потоков нужно для параллельного прогона тестов?
Столько, сколько сценариев с CAPTCHA идёт одновременно. Пять параллельных тестов — пять потоков (BASIC, $15/мес). Общее число решений за месяц роли не играет: ограничена только одновременность.
Почему res.php долго отвечает CAPCHA_NOT_READY?
Это нормальное состояние очереди: задача принята, результат ещё не готов. Опрашивайте раз в 5 секунд и не чаще — частый опрос ничего не ускоряет. Если состояние держится дольше пары минут, проверьте, что pageurl совпадает с фактическим адресом страницы.
Можно ли обойтись без Selenium и работать только с API?
Да, если sitekey и URL известны заранее, а форма уходит обычным POST-запросом. Selenium нужен там, где параметры проверки видны только после рендера JavaScript.
Токен вставился, но страница не реагирует — что проверить?
Скорее всего, форма построена на обратном вызове: найдите callback через ___grecaptcha_cfg.clients и вызовите его с токеном вместо submit(). Второй частый случай — поле g-recaptcha-response внутри iframe: тогда нужен switch_to.frame.
Меняется ли что-то в headless-режиме?
Для решения проверки — нет: токен приходит от сервиса, браузеру достаточно отрисовать страницу. Разница проявляется в вёрстке — задавайте window-size явно.