Если вы уже пробовали автоматически проверять тарифы авиакомпаний по расписанию, то знаете: циклический опрос быстро упирается в reCAPTCHA v2 или Cloudflare Turnstile — сайты реагируют на повторяющиеся запросы с одного IP уже после нескольких обращений. Решение не в отказе от автоматизации, а в отдельном шаге решения CAPTCHA внутри конвейера. Именно его берёт на себя CaptchaAI: получает токен reCAPTCHA или Turnstile через API, пока скрипт мониторинга продолжает собирать данные о ценах.
Ниже — рабочий процесс мониторинга, который сам обнаруживает и решает CAPTCHA во время проверки цен: от запроса по расписанию до алерта об изменении цены.
Особенно заметно это на маршрутах из России, Казахстана и Беларуси в Европу: сайт авиакомпании видит нетипичный всплеск запросов с одного диапазона IP и подключает reCAPTCHA или Turnstile быстрее, чем при обычном браузерном трафике. Дальше вопрос не в том, чтобы полностью убрать проверку из процесса, а в том, чтобы решать её штатным API-запросом и не терять окно мониторинга.
Как устроен пайплайн мониторинга
Schedule check → Request fare page → CAPTCHA detected?
↓ Yes
Solve via CaptchaAI → Inject token → Retry request
↓ No
Parse fare data → Store → Alert on price change
Каждый шаг пайплайна отвечает за одну задачу:
- Планировщик по расписанию запускает проверку маршрута.
- Скрипт запрашивает страницу тарифа обычным HTTP-запросом.
- Если в ответе обнаружена CAPTCHA — переход к решению через CaptchaAI, иначе сразу к парсингу.
- CaptchaAI возвращает токен, скрипт подставляет его в исходный запрос и повторяет его.
- Тариф парсится, сохраняется в истории, и при изменении цены уходит алерт.
Что понадобится для запуска
- API-ключ CaptchaAI — получите на captchaai.com.
- Python 3.8+ — с библиотекой
requests. - Прокси — резидентный прокси для туристических сайтов.
pip install requests
Совет: если мониторите больше 10–15 маршрутов одновременно, сразу берите пул из нескольких резидентных прокси на ротации — один статический IP с таким объёмом запросов сайты авиакомпаний блокируют в течение первого дня.
Функции для решения CAPTCHA через API CaptchaAI
Две функции ниже отправляют задачу в in.php (reCAPTCHA v2 или Turnstile), затем опрашивают res.php каждые 5 секунд, пока не получат токен или не истечёт тайм-аут.
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_recaptcha_v2(sitekey, pageurl):
"""Solve reCAPTCHA v2 and return the token."""
submit = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY, "method": "userrecaptcha",
"googlekey": sitekey, "pageurl": pageurl, "json": 1
}).json()
if submit.get("status") != 1:
raise RuntimeError(f"Submit error: {submit.get('request')}")
task_id = submit["request"]
time.sleep(20)
for _ in range(30):
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": 1
}).json()
if result.get("status") == 1:
return result["request"]
if result.get("request") != "CAPCHA_NOT_READY":
raise RuntimeError(f"Solve error: {result['request']}")
time.sleep(5)
raise TimeoutError("Solve timed out")
def solve_turnstile(sitekey, pageurl):
"""Solve Cloudflare Turnstile and return the token."""
submit = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY, "method": "turnstile",
"sitekey": sitekey, "pageurl": pageurl, "json": 1
}).json()
if submit.get("status") != 1:
raise RuntimeError(f"Submit error: {submit.get('request')}")
task_id = submit["request"]
time.sleep(10)
for _ in range(30):
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": 1
}).json()
if result.get("status") == 1:
return result["request"]
if result.get("request") != "CAPCHA_NOT_READY":
raise RuntimeError(f"Solve error: {result['request']}")
time.sleep(5)
raise TimeoutError("Solve timed out")
Класс FareMonitor: логика проверки тарифов
FareMonitor запрашивает страницу маршрута, проверяет, не подставлена ли вместо тарифа CAPTCHA, при необходимости решает её и только затем парсит цену. Токен reCAPTCHA v2 идёт в поле g-recaptcha-response, токен Turnstile — в cf-turnstile-response; имена полей менять нельзя, иначе сайт отклонит запрос.
import json
from datetime import datetime
class FareMonitor:
def __init__(self, proxy=None):
self.session = requests.Session()
if proxy:
self.session.proxies = {
"http": f"http://{proxy}",
"https": f"http://{proxy}"
}
self.fare_history = {}
def check_fare(self, route):
"""Check fare for a route, solving CAPTCHAs if needed."""
url = route["url"]
response = self.session.get(url)
# Detect CAPTCHA in response
if self._has_recaptcha(response.text):
sitekey = self._extract_sitekey(response.text)
token = solve_recaptcha_v2(sitekey, url)
response = self.session.post(url, data={
"g-recaptcha-response": token,
**route.get("params", {})
})
elif self._has_turnstile(response.text):
sitekey = self._extract_turnstile_key(response.text)
token = solve_turnstile(sitekey, url)
response = self.session.post(url, data={
"cf-turnstile-response": token,
**route.get("params", {})
})
return self._parse_fare(response.text, route)
def _has_recaptcha(self, html):
return "g-recaptcha" in html or "recaptcha/api" in html
def _has_turnstile(self, html):
return "cf-turnstile" in html or "turnstile" in html
def _extract_sitekey(self, html):
# Extract data-sitekey from reCAPTCHA div
if 'data-sitekey="' in html:
start = html.index('data-sitekey="') + 14
end = html.index('"', start)
return html[start:end]
return None
def _extract_turnstile_key(self, html):
if 'data-sitekey="' in html:
idx = html.index("cf-turnstile")
start = html.index('data-sitekey="', idx) + 14
end = html.index('"', start)
return html[start:end]
return None
def _parse_fare(self, html, route):
"""Parse fare data from the response. Customize per target site."""
# Placeholder — implement per site
return {
"route": route["name"],
"timestamp": datetime.now().isoformat(),
"raw_length": len(html)
}
def monitor_routes(self, routes):
"""Check all routes and report price changes."""
results = []
for route in routes:
try:
fare = self.check_fare(route)
results.append(fare)
print(f"[OK] {route['name']}: checked")
except Exception as e:
print(f"[ERROR] {route['name']}: {e}")
return results
# Usage
routes = [
{
"name": "NYC-LAX",
"url": "https://example-airline.com/search?from=JFK&to=LAX&date=2025-08-15",
"params": {"adults": 1}
},
{
"name": "SFO-ORD",
"url": "https://example-airline.com/search?from=SFO&to=ORD&date=2025-08-20",
"params": {"adults": 1}
}
]
monitor = FareMonitor(proxy="user:[email protected]:8080")
results = monitor.monitor_routes(routes)
for r in results:
print(json.dumps(r, indent=2))
Что делает каждый метод класса
check_fare— запрашивает страницу маршрута и решает, нужен ли вызов CaptchaAI, прежде чем парсить ответ._has_recaptcha/_has_turnstile— определяют тип CAPTCHA по маркерам в HTML, не дожидаясь явной ошибки._extract_sitekey/_extract_turnstile_key— вытаскиваютsitekeyиз разметки страницы для передачи в CaptchaAI._parse_fare— точка расширения: замените заглушку на реальный парсинг тарифа под конкретный сайт.monitor_routes— прогоняет весь список маршрутов и логирует результат по каждому, не прерывая цикл при ошибке одного маршрута.
Типичные проблемы и решения
Прежде чем ставить монитор на расписание, стоит один раз прогнать его вручную и свериться с этим списком:
- CAPTCHA появляется слишком часто — обычно это много запросов с одного IP; используйте ротацию резидентных прокси.
- Тариф не обновляется — страница отдаётся из кеша; добавьте параметр, сбивающий кеш, или рандомизируйте query-параметры.
- IP заблокирован — сработало ограничение частоты запросов; увеличьте паузы между проверками и ротируйте прокси.
- CAPTCHA не решается — sitekey извлечён неверно; убедитесь, что он соответствует виджету CAPTCHA на странице.
Расписание запуска через cron
Запускайте монитор по расписанию — через cron или системный планировщик задач:
# Check fares every 6 hours
0 */6 * * * cd /path/to/project && python fare_monitor.py >> /var/log/fares.log 2>&1
Чек-лист сбора данных о тарифах
- Сохраняйте базовый тариф, налоги, валюту и класс тарифа в одной записи — так проще сравнивать историю цен позже.
- Держите минимум два запасных селектора и сохраняйте сырой HTML-снимок страницы на случай, если парсинг не сработает.
- Логируйте маршрут, дату вылета, время проверки и задержку решения CAPTCHA при каждом запуске.
Агентство, которое отслеживает тарифы по десяткам маршрутов из Москвы, Алматы или Минска в Европу и Азию, обычно фиксирует именно этот набор полей — этого достаточно, чтобы поймать разовое падение цены и не спутать его со сбоем парсинга.
Вопросы про мониторинг тарифов авиакомпаний
Сколько потоков CaptchaAI нужно для мониторинга нескольких маршрутов одновременно?
Один поток решает одну CAPTCHA единовременно. Для 20–50 маршрутов с интервалом в несколько часов обычно хватает STANDARD ($30/мес, 15 потоков); при более плотном расписании или росте числа маршрутов берите ADVANCE ($90/мес, 50 потоков).
Как отличить реальное изменение тарифа от сбоя парсинга?
Сохраняйте вместе с каждым тарифом сырой HTML-снимок и код ответа. Если цена резко изменилась, а снимок выглядит иначе — другая вёрстка, ошибка 403 или заглушка вместо результатов — это сбой парсинга, а не реальное движение цены.
Какие типы CAPTCHA чаще всего встречаются на сайтах авиакомпаний?
Чаще всего — reCAPTCHA v2 и Cloudflare Turnstile, реже — полноэкранная страница Cloudflare Challenge («Checking your browser…») и изредка CAPTCHA с изображениями.
Как обрабатывать Cloudflare Challenge в этом пайплайне?
Cloudflare Challenge — не Turnstile: это полноэкранная страница вместо встроенного виджета. Отправьте задачу с method=cloudflare_challenge и обязательным прокси; в ответ придёт cookie cf_clearance, которую нужно подставить в сессию вместе с тем же прокси и User-Agent — иначе Cloudflare покажет проверку снова. Подробнее — в руководстве по решению Cloudflare Challenge.
Что делать, если авиакомпания меняет вёрстку страницы с тарифами?
Обновите _parse_fare и селекторы, держите про запас резервный вариант — сохранённый HTML-снимок из чек-листа сразу покажет, что изменилось в разметке.
Начните мониторинг тарифов с CaptchaAI
Получите API-ключ на captchaai.com и подключите решение CAPTCHA к пайплайну проверки тарифов.