Парсер редко ломается на разметке. Он ломается в тот момент, когда сервер вместо данных отдаёт страницу с проверкой, а код продолжает раскладывать её по селекторам и писать в базу пустые строки. Практический ответ короткий: парсер должен распознавать проверку прямо в HTTP-ответе, отправлять её в API CaptchaAI и повторять исходный запрос уже с токеном. Остальной пайплайн при этом трогать не нужно.
Ниже разобраны три ситуации, которые покрывают почти любой сбор данных: проверка появляется изредка, проверка стоит на конкретной странице всегда и вся площадка закрыта Cloudflare. Примеры на Python, но логика одинакова для Node.js, Go и PHP — in.php принимает задачу, res.php отдаёт токен.
Как понять, что вместо данных пришла проверка
Первый шаг — не решение, а обнаружение: пока парсер не отличает страницу с данными от страницы с проверкой, логика повторов работает вслепую. Надёжные маркеры в теле ответа:
g-recaptchaи атрибутdata-sitekeyв разметке — reCAPTCHA v2;- контейнер
cf-turnstileи скрытое полеcf-turnstile-response— Cloudflare Turnstile; - HTTP 403 или 503 с коротким HTML вместо ожидаемого документа — полностраничная проверка Cloudflare;
- картинка в base64 рядом с текстовым полем и коротким
<form>— классическая image/OCR-проверка.
Заведите метрику «доля ответов с проверкой» и пишите её в логи: её рост — раннее предупреждение о том, что площадка изменила настройки защиты.
Какие проверки CaptchaAI решает, а какие нет
| Тип проверки | Метод CaptchaAI | Где встречается при парсинге |
|---|---|---|
| reCAPTCHA v2 | method=userrecaptcha |
формы входа, страницы поиска |
| reCAPTCHA v3 | method=userrecaptcha&version=v3 |
фоновая оценка на любой странице |
| Cloudflare Turnstile | method=turnstile |
сайты за Cloudflare |
| Полностраничная проверка Cloudflare | method=turnstile |
блокирующая страница на весь домен |
| Изображение / OCR | method=base64 |
устаревшие формы, архивы, каталоги |
| GeeTest v3 | method=geetest |
площадки с азиатским анти-бот стеком |
| hCaptcha | ❌ не поддерживается | сайты, ориентированные на приватность |
| GeeTest v4 | ❌ пока нет, заявлено как «скоро» | новые сборки GeeTest |
CaptchaFox, Friendly Captcha и Lemin доступны в статусе beta — закладывать их в продакшн-парсер как штатный шаг пока рано. Если целевая площадка стоит на hCaptcha или FunCaptcha (Arkose Labs), честный вывод один: этот источник данных придётся закрывать другим способом.
Подход 1: решать проверку по факту появления
Самый экономный сценарий. Парсер работает обычным образом, а решение запрашивается только тогда, когда проверка реально пришла в ответе. Так вы не тратите потоки на страницы, которые отдаются свободно.
Класс ниже держит одну сессию requests с постоянным набором заголовков, проверяет ответ на маркеры, вытаскивает sitekey из разметки и отправляет задачу в in.php. Метод _poll опрашивает res.php раз в пять секунд, пока не получит OK| или ошибку.
import requests
import time
from bs4 import BeautifulSoup
API_KEY = "YOUR_API_KEY"
class ProtectedScraper:
def __init__(self):
self.session = requests.Session()
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})
def scrape(self, url):
resp = self.session.get(url)
# Check for CAPTCHA
if self._has_captcha(resp.text):
resp = self._handle_captcha(resp.text, url)
return resp.text
def _has_captcha(self, html):
indicators = ["g-recaptcha", "cf-turnstile", "h-captcha", "captcha"]
return any(ind in html.lower() for ind in indicators)
def _handle_captcha(self, html, url):
soup = BeautifulSoup(html, "html.parser")
# reCAPTCHA v2
rc = soup.find("div", class_="g-recaptcha")
if rc:
token = self._solve_recaptcha(rc["data-sitekey"], url)
return self.session.post(url, data={"g-recaptcha-response": token})
# Cloudflare Turnstile
ts = soup.find("div", class_="cf-turnstile")
if ts:
token = self._solve_turnstile(ts["data-sitekey"], url)
return self.session.post(url, data={"cf-turnstile-response": token})
raise Exception("Unknown CAPTCHA type")
def _solve_recaptcha(self, 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
})
return self._poll(resp.text.split("|")[1])
def _solve_turnstile(self, 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
})
return self._poll(resp.text.split("|")[1])
def _poll(self, task_id):
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(result.text)
raise TimeoutError()
# Usage
scraper = ProtectedScraper()
html = scraper.scrape("https://example.com/data")
Ключевая деталь — токен возвращается в то же поле, которого ждёт форма: g-recaptcha-response для reCAPTCHA и cf-turnstile-response для Turnstile. Если подставить его не туда, сервер молча отдаст ту же страницу с проверкой, и парсер зациклится.
Подход 2: решать заранее там, где проверка стоит всегда
Некоторые страницы — поиск, экспорт, форма фильтра — показывают проверку каждый раз. Ходить за ними дважды бессмысленно: запросите токен и сразу отправляйте форму вместе с ним.
def scrape_known_captcha_page(url, site_key):
# Solve before even loading the page
token = solve_recaptcha(site_key, url)
# Submit directly with token
resp = requests.post(url, data={
"g-recaptcha-response": token,
"query": "search term"
})
return resp.text
Важная оговорка: токен запрашивается непосредственно перед отправкой, а не складывается в запас. Время жизни у него короткое, и попытка накопить решения впрок даёт пачку просроченных токенов.
Подход 3: страницы за Cloudflare
Если Cloudflare закрывает домен целиком, отдельного поля формы нет — результат проверки фиксируется в cookie, которую нужно донести до всех последующих запросов.
def get_cloudflare_clearance(url, proxy):
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "turnstile",
"pageurl": url,
"proxy": proxy,
"proxytype": "HTTP"
})
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 "qa_validation_cookie" in result.text:
# Parse qa_validation_cookie and user_agent from response
return result.text
raise TimeoutError()
Прокси здесь указывается по технической причине: проверка решается с того же маршрута, с которого пойдут дальнейшие запросы. Смените прокси или User-Agent между решением и запросом — и cookie перестанет соответствовать сессии.
Пагинация: собрать десятки страниц и не потерять данные
Одна упавшая страница не должна ронять весь проход. Обрабатывайте исключение внутри цикла, логируйте номер страницы и количество найденных элементов, а между запросами держите случайную паузу — это снижает нагрузку на площадку и заодно уменьшает частоту проверок.
def scrape_multiple_pages(base_url, site_key, pages):
scraper = ProtectedScraper()
results = []
for page in pages:
url = f"{base_url}?page={page}"
try:
html = scraper.scrape(url)
soup = BeautifulSoup(html, "html.parser")
items = soup.find_all("div", class_="item")
results.extend([item.text.strip() for item in items])
print(f"Page {page}: {len(items)} items")
except Exception as e:
print(f"Page {page} failed: {e}")
time.sleep(random.uniform(2, 5))
return results
Для длинных проходов сохраняйте промежуточный результат на диск каждые N страниц: ночной сбор на нестабильном канале — обычная ситуация, и терять четыре часа работы из-за разрыва соединения не хочется.
Сколько потоков нужно парсеру
CaptchaAI тарифицируется по потокам, а не по числу решений: поток — это одна проверка в работе, количество решений в месяц не ограничено. Cloudflare Turnstile, по данным CaptchaAI, решается менее чем за 10 секунд, то есть пять параллельных потоков дают ориентировочно 1800 проверок в час. Отсюда практическое сопоставление:
- одиночный парсер на одной-двух машинах — BASIC ($15/мес, 5 потоков) или STANDARD ($30/мес, 15 потоков);
- регулярный сбор с десятков воркеров — ADVANCE ($90/мес, 50 потоков);
- постоянные ночные проходы по крупным каталогам — PREMIUM ($170/мес, 100 потоков) и выше.
Для фрилансеров и агентств, которые считают расходы в валюте с высокой волатильностью, предсказуемый месячный платёж в USD часто важнее цены за отдельное решение: бюджет проекта фиксируется на старте.
Что проверить, если парсер всё равно не проходит
| Симптом | Что делать |
|---|---|
| Проверка появляется на каждой странице | снизьте частоту запросов и разнесите проход по времени |
| Токен отклонён сразу после решения | срок его жизни короткий; отправляйте токен в пределах 120 секунд после получения |
| Cloudflare блокирует даже с полученной cookie | используйте один и тот же прокси и User-Agent и при решении, и при последующих запросах |
| После прохождения приходит не та страница | проверьте промежуточные редиректы и cookie, которые ставит форма |
| HTTP 200, но нужные поля пустые | контент дорисовывается на JavaScript — нужен браузерный движок, а не голый HTTP-клиент |
Собирайте только те данные, которые вправе обрабатывать
Техническая возможность получить страницу и право хранить её содержимое — разные вещи. Перед регулярным сбором посмотрите условия использования площадки и robots.txt, а если в выгрузку попадают персональные данные — оцените свои обязанности по 152-ФЗ «О персональных данных» или по применимому к вам регламенту. Правило простое: собирайте только те поля, которые вам действительно нужны, и фиксируйте, зачем нужен каждый атрибут.
Частые вопросы
Почему проверка появилась там, где её раньше не было?
Чаще всего меняются настройки защиты на стороне площадки, а поводом становится характер трафика: одинаковый интервал между запросами, один IP на весь проход, отсутствие обычных заголовков. Начните с частоты и распределения запросов.
Сколько живёт токен и почему сервер его не принимает?
Практический ориентир — 120 секунд. Если между получением токена и отправкой формы парсер успевает сходить ещё за тремя страницами, токен приедет просроченным. Вторая частая причина — токен подставлен не в то поле или отправлен на другой URL, чем тот, который был указан в pageurl.
Как подключить решение проверки к парсеру на Selenium или Playwright?
Схема та же: браузер рендерит страницу, вы забираете sitekey из DOM, отправляете задачу в API, а полученный токен вставляете в скрытое поле и вызываете отправку формы. Подробный разбор с кодом — в материале обработка CAPTCHA в Selenium на Python.
Что делать, если стоит hCaptcha?
Менять источник данных: этот тип не поддерживается — как и FunCaptcha (Arkose Labs). Поддерживаются reCAPTCHA v2 и v3, Cloudflare Turnstile и Cloudflare Challenge, GeeTest v3, image/OCR, grid и BLS; CaptchaFox, Friendly Captcha и Lemin — в статусе beta.