Скрипт на requests обрабатывает сотни страниц в секунду — и встаёт, как только сайт показывает CAPTCHA. Ниже рабочая схема на Python: снять sitekey со страницы, отправить в CaptchaAI, получить токен и продолжить парсинг без Selenium для большинства сайтов. Никакого браузера, никакого рендеринга JavaScript — только requests, BeautifulSoup и один вспомогательный класс, который берёт на себя обмен с API CaptchaAI.
Типичный кейс — мониторинг цен на маркетплейсах или сбор данных с порталов, где форма поиска закрыта reCAPTCHA v3, а карточки товаров доступны только после прохождения проверки. Другой частый сценарий — парсинг форм на визовых и государственных порталах с текстовыми CAPTCHA на картинках вместо reCAPTCHA. Перед запуском проверьте, что собираете только данные, на обработку которых вправе рассчитывать: 152-ФЗ «О персональных данных» для российской аудитории, GDPR-аккуратность для трансграничных проектов. Собирайте только те поля, которые нужны для задачи, и не сохраняйте персональные данные без основания. Это касается и логов парсера: не пишите в них токены и полные ответы форм дольше, чем нужно для отладки.
Что понадобится
- Python 3.7+ — с
pipвPATH. - requests —
pip install requests; отвечает и за запросы к целевому сайту, и за обмен с API CaptchaAI. - beautifulsoup4 —
pip install beautifulsoup4; разбирает HTML страницы с результатами и достаётsitekey. - API-ключ CaptchaAI — возьмите в личном кабинете captchaai.com. Без ключа
_submitв классе ниже вернётERROR_WRONG_USER_KEY.
Класс-решатель CaptchaAI
Оберните вызовы API в отдельный класс — тогда его можно переиспользовать в любом парсере без дублирования кода. Метод _submit отправляет параметры задачи на in.php и возвращает id, а _poll каждые пять секунд опрашивает res.php, пока сервис не вернёт готовый токен или не истечёт таймаут:
import requests
import time
class CaptchaSolver:
def __init__(self, api_key):
self.api_key = api_key
self.base = "https://ocr.captchaai.com"
def _submit(self, params):
params["key"] = self.api_key
resp = requests.get(f"{self.base}/in.php", params=params)
if not resp.text.startswith("OK|"):
raise Exception(f"Submit error: {resp.text}")
return resp.text.split("|")[1]
def _poll(self, task_id, timeout=300):
deadline = time.time() + timeout
while time.time() < deadline:
time.sleep(5)
resp = requests.get(f"{self.base}/res.php", params={
"key": self.api_key,
"action": "get",
"id": task_id
})
if resp.text == "CAPCHA_NOT_READY":
continue
if resp.text.startswith("OK|"):
return resp.text.split("|")[1]
raise Exception(f"Solve error: {resp.text}")
raise TimeoutError("Solve timed out")
def solve_recaptcha_v2(self, site_key, page_url):
task_id = self._submit({
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url
})
return self._poll(task_id)
def solve_recaptcha_v3(self, site_key, page_url, action="verify"):
task_id = self._submit({
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url,
"version": "v3",
"action": action
})
return self._poll(task_id)
def solve_turnstile(self, site_key, page_url):
task_id = self._submit({
"method": "turnstile",
"sitekey": site_key,
"pageurl": page_url
})
return self._poll(task_id)
def solve_image(self, image_base64):
task_id = self._submit({
"method": "base64",
"body": image_base64
})
return self._poll(task_id)
Парсинг формы с reCAPTCHA: пошагово
Важно:
pageurlв запросе к CaptchaAI должен быть тем же адресом, на котором реально показан виджет reCAPTCHA, — иначе задача решится, но токен сайт не примет.
Дальше — рабочий пример: берём sitekey со страницы, решаем reCAPTCHA v2 классом выше и отправляем токен вместе с формой:
from bs4 import BeautifulSoup
import requests
solver = CaptchaSolver("YOUR_API_KEY")
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})
# Step 1: Load the page
url = "https://example.com/search"
page = session.get(url)
soup = BeautifulSoup(page.text, "html.parser")
# Step 2: Extract the site key
recaptcha_div = soup.find("div", class_="g-recaptcha")
site_key = recaptcha_div["data-sitekey"]
# Step 3: Solve the CAPTCHA
token = solver.solve_recaptcha_v2(site_key, url)
# Step 4: Submit the form with the token
form_data = {
"q": "search term",
"g-recaptcha-response": token
}
result = session.post(url, data=form_data)
# Step 5: Parse the results
result_soup = BeautifulSoup(result.text, "html.parser")
items = result_soup.find_all("div", class_="result-item")
for item in items:
print(item.text.strip())
Обход пагинации, если CAPTCHA стоит на каждой странице
Если проверка возвращается на каждой странице листинга, оборачивайте вызов решателя в цикл. Каждая решённая CAPTCHA стоит одного слота из тарифа — при большом числе страниц имеет смысл заранее прикинуть, сколько потоков понадобится, чтобы парсер не упирался в очередь:
def scrape_all_pages(base_url, site_key, max_pages=10):
solver = CaptchaSolver("YOUR_API_KEY")
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})
all_results = []
for page_num in range(1, max_pages + 1):
page_url = f"{base_url}?page={page_num}"
# Solve CAPTCHA for each page if needed
token = solver.solve_recaptcha_v2(site_key, page_url)
resp = session.get(page_url, params={
"g-recaptcha-response": token,
"page": page_num
})
soup = BeautifulSoup(resp.text, "html.parser")
items = soup.find_all("div", class_="item")
if not items:
break
all_results.extend([item.text.strip() for item in items])
print(f"Page {page_num}: {len(items)} items")
time.sleep(2) # Polite delay
return all_results
Текстовые CAPTCHA на картинках
Часть старых форм показывает картинку с текстом вместо reCAPTCHA — метод base64 решает и такие. На практике это чаще всего:
- старые формы логина и регистрации, которые ещё не перешли на reCAPTCHA или Turnstile;
- визовые и государственные порталы вроде BLS-подобных форм записи, где текстовая CAPTCHA — единственная защита;
- внутренние панели администрирования с простой проверкой «введите текст с картинки».
import base64
def scrape_with_image_captcha(url):
solver = CaptchaSolver("YOUR_API_KEY")
session = requests.Session()
page = session.get(url)
soup = BeautifulSoup(page.text, "html.parser")
# Find the CAPTCHA image
captcha_img = soup.find("img", {"id": "captcha-image"})
captcha_url = captcha_img["src"]
# Download and encode the image
img_resp = session.get(captcha_url)
img_base64 = base64.b64encode(img_resp.content).decode()
# Solve
captcha_text = solver.solve_image(img_base64)
# Submit
form_data = {
"captcha": captcha_text,
"username": "user"
}
result = session.post(url, data=form_data)
return result.text
Повторные попытки при сетевых сбоях
Сеть и внешний API не всегда отвечают с первого раза, особенно на нестабильном канале или при работе через мобильный/резидентный прокси. Добавьте несколько попыток вместо того, чтобы ронять весь процесс на первой ошибке — три попытки с паузой обычно закрывают подавляющее большинство временных сбоев без заметной задержки для всего парсера:
def solve_with_retry(solver, site_key, page_url, max_retries=3):
for attempt in range(max_retries):
try:
return solver.solve_recaptcha_v2(site_key, page_url)
except Exception as e:
if attempt == max_retries - 1:
raise
print(f"Attempt {attempt + 1} failed: {e}. Retrying...")
time.sleep(2)
Типичные ошибки и как их читать
Общее правило: сначала проверьте баланс и имя поля токена — это закрывает большинство ошибок из списка ниже.
ERROR_WRONG_USER_KEY— неверный или устаревший API-ключ. Сверьте ключ в панели управления CaptchaAI.ERROR_ZERO_BALANCE— на балансе нет средств. Пополните баланс, прежде чем повторять запрос.- Форма снова показывает CAPTCHA — токен просрочен или отправлен не под тем именем поля. Отправляйте токен сразу после получения и сверьте имя поля.
ConnectionError— обрыв сети или таймаут на стороне CaptchaAI. Добавьте повтор с экспоненциальной задержкой, как в примере выше.- Пустой результат после успешной отправки — сайту нужны cookies сессии. Используйте
requests.Session(), чтобы cookies сохранялись между запросами.
Частые вопросы
Сколько потоков CaptchaAI нужно для парсера с CAPTCHA на каждой странице?
Зависит от параллелизма, а не от числа страниц: BASIC ($15/мес, 5 потоков) хватает для последовательного парсинга, ADVANCE ($90/мес, 50 потоков) — для пайплайна с десятками воркеров.
Обязателен ли Selenium, если форма отправляется обычным POST-запросом?
Нет. При стандартном HTTP POST связка requests + CaptchaAI работает быстрее и легче, чем Selenium. Браузер нужен только там, где без рендеринга JavaScript нет нужной разметки.
Что делать, если сайт снова показывает CAPTCHA после отправки токена?
Чаще всего токен устарел — отправляйте его сразу и сверьте имя поля (g-recaptcha-response для reCAPTCHA, cf-turnstile-response для Turnstile). Не помогло — проверьте cookies сессии.
Нужно ли передавать User-Agent и другие заголовки вместе с токеном?
Да. Многие сайты сверяют не только сам токен, но и заголовки запроса, которым он отправлен: реалистичный User-Agent, тот же Referer, что и при загрузке формы, и cookies текущей сессии из requests.Session(). Токен без этого контекста часть сайтов отклонит.
Как решать CAPTCHA параллельно и не терять скорость при частых запросах?
Замените requests на aiohttp — см. интеграцию aiohttp с CaptchaAI. От блокировки спасают паузы между запросами и ротация прокси — см. ротацию прокси при парсинге.
Что читать дальше
- Обработка CAPTCHA через Selenium на Python
- Парсинг с CAPTCHA на Node.js
- Как не терять доступ при парсинге: устойчивость к блокировкам