Форма не отправится, пока не пройдена CAPTCHA — на этом шаге автоматизация обычно и встаёт. Ниже — связка на Selenium и API CaptchaAI: скрипт сам определяет тип защиты (reCAPTCHA v2, Turnstile или картинка), решает её через API и отправляет форму без ручного вмешательства.
Почему CAPTCHA останавливает автоматизацию форм
Контактные формы, заявки, регистрация — везде, где есть публичная форма, рано или поздно появляется CAPTCHA против ботов. Пока проверка не решена, тест зависает на этом шаге: Selenium честно заполняет поля, жмёт кнопку отправки — а сервер молча отклоняет запрос, потому что скрытое поле токена так и осталось пустым. Без отдельного модуля, который умеет распознать тип защиты и получить токен через API, любой сценарий с формой рано или поздно упирается в этот шаг, и весь набор регрессионных тестов становится нестабильным именно из-за CAPTCHA, а не из-за багов в самом продукте.
Практическая логика простая: сначала определить, какая защита стоит на конкретной странице (она может отличаться от формы к форме и меняться при обновлении фронтенда), затем получить решение через API и подставить его туда, куда ждёт форма — обычно в скрытый input, реже через явный вызов callback-функции. Дальше форма отправляется как обычно.
Практический сценарий: ночной прогон регрессии в CI
Например, ночной регрессионный прогон в CI против staging с формами за Turnstile: 10 параллельных сценариев укладываются в STANDARD ($30/мес, 15 потоков) — каждый воркер Selenium держит один поток, пока страница загружается, форма заполняется и решается CAPTCHA. Если параллельных сценариев больше 15, часть заданий просто встанет в очередь и увеличит общее время прогона, поэтому число потоков стоит планировать исходя из реального числа одновременных браузерных сессий, а не из числа тестовых сценариев в файле.
Если тестовые данные включают реальные email или другие персональные данные, сверьтесь с требованиями 152-ФЗ «О персональных данных» и используйте только те данные, которые вы вправе обрабатывать в тестовом контуре; для команд с международной аудиторией уместна та же дисциплина в духе GDPR — тестовые окружения не должны становиться местом хранения реальных персональных данных без явного основания.
Архитектура пайплайна
┌────────────┐ ┌──────────────┐ ┌────────────┐ ┌──────────────┐
│ Load Form │────▶│ Fill Fields │────▶│ Detect & │────▶│ Submit Form │
│ (Selenium) │ │ │ │ Solve │ │ │
│ │ │ │ │ CAPTCHA │ │ │
└────────────┘ └──────────────┘ └────────────┘ └──────────────┘
Из чего состоит решение
Пайплайн собирается из трёх независимых классов, каждый отвечает за свою часть: один решает CAPTCHA через API, второй определяет, какая защита стоит на странице, третий связывает всё воедино и управляет самой формой. Такое разделение позволяет переиспользовать модуль решения CAPTCHA в других сценариях (не только для форм) и менять логику детектора, не трогая остальной код.
Модуль решения CAPTCHA
Отправляет задачу в in.php и опрашивает res.php, пока результат не будет готов. initial_wait задаёт паузу перед первым опросом — большинство типов CAPTCHA не решаются быстрее нескольких секунд, поэтому опрашивать res.php сразу после отправки задачи бессмысленно и только тратит лимит запросов.
import time
import requests
class FormCaptchaSolver:
BASE = "https://ocr.captchaai.com"
def __init__(self, api_key):
self.api_key = api_key
def solve(self, params, initial_wait=10):
params["key"] = self.api_key
params["json"] = 1
resp = requests.post(f"{self.BASE}/in.php", data=params).json()
if resp["status"] != 1:
raise Exception(f"Submit error: {resp['request']}")
task_id = resp["request"]
time.sleep(initial_wait)
for _ in range(60):
result = requests.get(
f"{self.BASE}/res.php",
params={"key": self.api_key, "action": "get", "id": task_id, "json": 1},
).json()
if result["request"] == "CAPCHA_NOT_READY":
time.sleep(5)
continue
if result["status"] == 1:
return result["request"]
raise Exception(f"Solve error: {result['request']}")
raise TimeoutError("CAPTCHA solve timed out")
Определение типа CAPTCHA на странице
Сначала нужно понять, с чем работает форма — с Turnstile, reCAPTCHA v2 или картинкой. Порядок проверок здесь важен: Turnstile идёт первым, потому что его виджет тоже использует data-sitekey и без явной проверки класса cf-turnstile детектор ошибочно примет его за reCAPTCHA. Если на странице вообще нет виджета с data-sitekey и нет img.captcha, метод возвращает "none" — это нормальный случай для форм без защиты, и solve_captcha() просто пропускает шаг решения.
import re
from selenium.webdriver.common.by import By
class CaptchaDetector:
def __init__(self, driver):
self.driver = driver
def detect(self):
"""Detect CAPTCHA type on current page."""
html = self.driver.page_source
# Turnstile
turnstile = self.driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile, [data-sitekey]")
for el in turnstile:
if "cf-turnstile" in (el.get_attribute("class") or ""):
return "turnstile", el.get_attribute("data-sitekey")
# reCAPTCHA
recaptcha = self.driver.find_elements(By.CSS_SELECTOR, "[data-sitekey]")
if recaptcha:
sitekey = recaptcha[0].get_attribute("data-sitekey")
if "recaptcha" in html.lower():
return "recaptcha_v2", sitekey
# Image CAPTCHA
img = self.driver.find_elements(By.CSS_SELECTOR, "img[src*='captcha'], img.captcha")
if img:
return "image", img[0].get_attribute("src")
return "none", None
Управление формой end-to-end
Связывает детектор и решатель: заполняет поля, подставляет токен в нужный input и отправляет форму. Для reCAPTCHA v2 и Turnstile токен присваивается через execute_script напрямую полю с соответствующим name (g-recaptcha-response или cf-turnstile-response) — так надёжнее, чем эмулировать клик по виджету, который сам по себе ничего не решает, а только показывает интерфейс. Для картиночной CAPTCHA solve_captcha() скачивает изображение, кодирует его в base64 и передаёт решателю с методом base64 — распознанный текст затем вводится в обычное текстовое поле как есть.
import base64
import requests as req
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class FormAutomator:
def __init__(self, api_key):
self.solver = FormCaptchaSolver(api_key)
self.driver = webdriver.Chrome()
self.detector = CaptchaDetector(self.driver)
def fill_field(self, selector, value):
field = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, selector))
)
field.clear()
field.send_keys(value)
def select_option(self, selector, value):
from selenium.webdriver.support.ui import Select
select = Select(self.driver.find_element(By.CSS_SELECTOR, selector))
select.select_by_value(value)
def solve_captcha(self):
captcha_type, data = self.detector.detect()
page_url = self.driver.current_url
if captcha_type == "recaptcha_v2":
token = self.solver.solve({
"method": "userrecaptcha",
"googlekey": data,
"pageurl": page_url,
})
self.driver.execute_script(
f'document.querySelector("[name=g-recaptcha-response]").value = "{token}";'
)
return True
if captcha_type == "turnstile":
token = self.solver.solve({
"method": "turnstile",
"sitekey": data,
"pageurl": page_url,
})
self.driver.execute_script(
f'document.querySelector("[name=cf-turnstile-response]").value = "{token}";'
)
return True
if captcha_type == "image":
img_data = req.get(data).content
img_b64 = base64.b64encode(img_data).decode()
text = self.solver.solve({"method": "base64", "body": img_b64})
captcha_input = self.driver.find_element(
By.CSS_SELECTOR, "input[name*='captcha']"
)
captcha_input.clear()
captcha_input.send_keys(text)
return True
return False # No CAPTCHA detected
def submit_form(self, url, fields, submit_selector="button[type='submit']"):
"""
fields: list of (selector, value) tuples
"""
self.driver.get(url)
for selector, value in fields:
self.fill_field(selector, value)
self.solve_captcha()
submit = self.driver.find_element(By.CSS_SELECTOR, submit_selector)
submit.click()
return self.driver.current_url
def close(self):
self.driver.quit()
Рабочий пример: форма обратной связи
Полный сценарий — от загрузки до отправки. FormAutomator сам открывает страницу, последовательно заполняет каждое поле из списка fields, вызывает solve_captcha() и только потом кликает по кнопке отправки — порядок важен, потому что токен CAPTCHA живёт ограниченное время и должен подставляться последним шагом перед кликом:
automator = FormAutomator("YOUR_API_KEY")
try:
result_url = automator.submit_form(
url="https://example.com/contact",
fields=[
("#name", "John Doe"),
("#email", "[email protected]"),
("#subject", "Sales inquiry"),
("#message", "I'd like to learn more about your services."),
],
submit_selector="#submit-btn",
)
print(f"Form submitted. Redirected to: {result_url}")
finally:
automator.close()
Адаптация под разные типы форм
Один и тот же submit_form() работает для входа, регистрации и поиска — меняются только селекторы, URL и набор полей, а логика определения и решения CAPTCHA остаётся общей. Это удобно для регрессионных наборов: один и тот же FormAutomator покрывает десятки сценариев без дублирования кода решения CAPTCHA.
Форма авторизации
result = automator.submit_form(
url="https://staging.example.com/qa-login",
fields=[
("#username", "testuser"),
("#password", "testpass123"),
],
submit_selector="#login-btn",
)
Такой сценарий покрывает и обычный вход, и восстановление после неудачной попытки — если логин отклонён, форма может подключить CAPTCHA только на повторной попытке (см. FAQ ниже про повторные вызовы solve_captcha()).
Форма регистрации
result = automator.submit_form(
url="https://example.com/register",
fields=[
("#first-name", "Jane"),
("#last-name", "Smith"),
("#email", "[email protected]"),
("#password", "SecurePass!123"),
("#confirm-password", "SecurePass!123"),
],
submit_selector="#register-btn",
)
Форма поиска
result = automator.submit_form(
url="https://example.com/search",
fields=[
("#query", "python developer"),
("#location", "San Francisco"),
],
submit_selector="#search-btn",
)
Поисковые формы с CAPTCHA встречаются реже, чем логин и регистрация, но именно на них чаще всего экономят при написании тестов — а зря: если поиск защищён от ботов, регрессионный прогон без модуля решения CAPTCHA будет молча падать на пустой выдаче вместо понятной ошибки.
Типичные проблемы и их решения
Что чаще всего идёт не так и как это чинить — таблица собрана по частым причинам падений в реальных прогонах, а не по гипотетическим случаям:
| Проблема | Причина | Решение |
|---|---|---|
| Токен CAPTCHA отклонён | Токен успел «протухнуть» до отправки формы | Решайте CAPTCHA последним шагом и отправляйте форму сразу |
| Поле не найдено на странице | Форма подгружается асинхронно | Добавьте явное ожидание (WebDriverWait) перед поиском поля |
| Определён не тот тип CAPTCHA | На странице несколько элементов с data-sitekey |
Проверьте порядок проверок в detect() |
| Форма перезагружается после отправки | Не прошла серверная валидация | Проверьте, что заполнены все обязательные поля |
| Callback reCAPTCHA не срабатывает | Нужно явно вызвать функцию обратного вызова | Вызовите grecaptcha.execute() после подстановки токена |
Часто задаваемые вопросы
Можно ли отправить форму с CAPTCHA вообще без браузера?
Для reCAPTCHA и Turnstile — да: решаете CAPTCHA через API, получаете токен и отправляете форму обычным POST-запросом, минуя Selenium целиком. Это заметно быстрее для прогонов на сотни форм. Но если форма проверяет JavaScript-состояние страницы или зависит от куки, выставленных скриптом, браузер всё же нужен.
Что делать, если CAPTCHA появляется только после неудачной попытки?
Некоторые формы подключают проверку только после первой ошибки отправки — например, после неверного пароля при логине. В этом случае одного вызова solve_captcha() в начале сценария недостаточно: запускайте его заново при каждой неудачной попытке, проверяя перед этим, появился ли на странице виджет CAPTCHA.
Сколько потоков CaptchaAI нужно для параллельного прогона тестов?
Ориентируйтесь на число одновременных сессий Selenium, а не на число тестовых сценариев в файле: 10–15 воркеров укладываются в STANDARD ($30/мес, 15 потоков), для крупных ночных прогонов на сотню и более сценариев — ADVANCE ($90/мес, 50 потоков). Если потоков не хватает, часть заданий будет просто ждать в очереди, и общее время прогона вырастет, а не упадёт с ошибкой.
Как логировать причину, если CAPTCHA не решилась вовремя?
Оборачивайте вызов solve() в try/except и пишите в лог task_id, тип CAPTCHA и фактическое время ожидания — так на разборе упавшего прогона сразу видно, был ли это сбой самого решения CAPTCHA, таймаут сети или упавший селектор Selenium, который вообще не добрался до формы.
Похожие материалы
Настройте эту связку под свои формы — решайте CAPTCHA через API CaptchaAI.