Use Cases

Обработка CAPTCHA для мониторинга статуса рейса

Данные о статусе рейса устаревают быстрее, чем реагирует большинство трекеров: Cloudflare Turnstile и reCAPTCHA срабатывают именно при повторных запросах с одного IP — ровно тогда, когда нужен свежий ответ. Ниже — где появляется CAPTCHA и как встроить её решение в опрос статуса, не превращая скрипт в цепочку хрупких костылей.

Практическое правило: если один и тот же IP опрашивает один и тот же рейс чаще, чем раз в 5 минут, Cloudflare Turnstile почти наверняка включится. Это первое, что стоит проверить, прежде чем чинить парсер.

Где на порталах авиакомпаний и аэропортов встречается CAPTCHA

Тип портала CAPTCHA Триггер
Страница статуса рейса авиакомпании Cloudflare Turnstile Частые запросы с одного IP
Табло вылета и прилёта аэропорта Cloudflare Challenge Обнаружение бота
Поисковики авиабилетов reCAPTCHA v2/v3 Отправка формы поиска
Проверка статуса бронирования reCAPTCHA v2 Перед показом маршрута
Страницы с ограничением частоты запросов API Пользовательская CAPTCHA После превышения лимита запросов

Практический сценарий: агрегатор статусов рейсов для СНГ-рынка

Travel-агентство с клиентами в России, Беларуси и Казахстане опрашивает 40–60 рейсов в час с серверов в Европе (собственная инфраструктура в РФ для внешних API-запросов обычно нестабильна из-за блокировок и скачков задержки). При интервале 15 минут доля запросов, упирающихся в CAPTCHA, держится на уровне 5–10% — это укладывается в STANDARD-план CaptchaAI. Основные направления мониторинга — рейсы через Стамбул, Дубай и Ереван как транзитные хабы для рынков РФ и СНГ, где авиакомпании особенно чувствительны к автоматическим запросам с одного диапазона IP. Если сервис хранит данные о пассажирах, а не только статус рейса, стоит свериться с 152-ФЗ (для операторов, работающих с гражданами РФ) или GDPR (если аудитория — ЕС).

Архитектура скрипта мониторинга рейсов

Базовый класс ниже держит одну сессию на весь цикл проверок, распознаёт и Cloudflare Turnstile, и reCAPTCHA по содержимому ответа, решает капчу через CaptchaAI и повторяет запрос — без пересоздания сессии на каждый вызов.

import requests
import time
from datetime import datetime

class FlightMonitor:
    def __init__(self, api_key):
        self.api_key = api_key
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
        })

    def check_flight(self, airline_url, flight_number):
        """Check flight status, handling CAPTCHAs if encountered."""
        response = self.session.get(
            f"{airline_url}/flight-status/{flight_number}"
        )

        if self._is_captcha_page(response):
            response = self._solve_and_retry(response, airline_url)

        return self._parse_flight_data(response.text)

    def _is_captcha_page(self, response):
        return (
            response.status_code == 403 or
            "cf-turnstile" in response.text or
            "g-recaptcha" in response.text
        )

    def _solve_and_retry(self, response, url):
        import re

        # Detect CAPTCHA type
        if "cf-turnstile" in response.text:
            match = re.search(r'data-sitekey="(0x[^"]+)"', response.text)
            token = self._solve_turnstile(match.group(1), url)
            field = "cf-turnstile-response"
        else:
            match = re.search(r'data-sitekey="([^"]+)"', response.text)
            token = self._solve_recaptcha(match.group(1), url)
            field = "g-recaptcha-response"

        return self.session.post(url, data={field: token})

    def _solve_turnstile(self, site_key, page_url):
        resp = requests.post("https://ocr.captchaai.com/in.php", data={
            "key": self.api_key,
            "method": "turnstile",
            "sitekey": site_key,
            "pageurl": page_url,
            "json": 1
        })
        task_id = resp.json()["request"]
        return self._poll_result(task_id)

    def _solve_recaptcha(self, site_key, page_url):
        resp = requests.post("https://ocr.captchaai.com/in.php", data={
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": site_key,
            "pageurl": page_url,
            "json": 1
        })
        task_id = resp.json()["request"]
        return self._poll_result(task_id)

    def _poll_result(self, task_id):
        for _ in range(60):
            time.sleep(3)
            result = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id,
                "json": 1
            })
            data = result.json()
            if data["status"] == 1:
                return data["request"]
        raise TimeoutError("CAPTCHA solve timed out")

    def _parse_flight_data(self, html):
        # Parse flight status from HTML
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")

        def text_or_none(node):
            return node.text.strip() if node and node.text else None

        return {
            "status": text_or_none(soup.select_one(".flight-status")),
            "departure": text_or_none(soup.select_one(".departure-time")),
            "arrival": text_or_none(soup.select_one(".arrival-time")),
            "gate": text_or_none(soup.select_one(".gate-info")),
            "checked_at": datetime.now().isoformat()
        }

Периодический опрос статуса с обработкой CAPTCHA

Дальше — обвязка вокруг FlightMonitor, которая опрашивает рейс с заданным интервалом, копит историю проверок и печатает изменение статуса, как только оно появляется.

def monitor_flight(monitor, airline_url, flight_number, 
                   interval_seconds=300, max_checks=48):
    """Monitor a flight every N seconds, handling CAPTCHAs as needed."""
    history = []

    for check_num in range(max_checks):
        try:
            status = monitor.check_flight(airline_url, flight_number)
            history.append(status)

            # Alert on changes
            if len(history) > 1 and status["status"] != history[-2]["status"]:
                print(f"Status changed: {history[-2]['status']} → {status['status']}")

            print(f"Check {check_num + 1}: {status['status']} "
                  f"(Gate: {status.get('gate', 'Coming soon')})")

        except Exception as e:
            print(f"Check {check_num + 1} failed: {e}")

        time.sleep(interval_seconds)

    return history

# Usage
monitor = FlightMonitor("YOUR_API_KEY")
monitor_flight(monitor, "https://airline.example.com", "AA1234")

Мониторинг нескольких авиакомпаний на JavaScript

Если рейсы нужно отслеживать сразу у нескольких авиакомпаний, удобнее держать один трекер с картой активных рейсов, чем плодить отдельные скрипты под каждый портал — ниже вариант на JavaScript с тем же принципом решения Turnstile just-in-time.

class FlightTracker {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.flights = new Map();
  }

  async addFlight(airline, flightNumber, checkUrl) {
    this.flights.set(flightNumber, {
      airline,
      url: checkUrl,
      history: [],
      lastCheck: null
    });
  }

  async checkAll() {
    const results = [];

    for (const [flightNum, flight] of this.flights) {
      try {
        const status = await this.checkFlight(flight.url, flightNum);
        flight.history.push(status);
        flight.lastCheck = new Date();
        results.push({ flight: flightNum, ...status });
      } catch (error) {
        results.push({ flight: flightNum, error: error.message });
      }
    }

    return results;
  }

  async checkFlight(url, flightNumber) {
    const response = await fetch(`${url}/status/${flightNumber}`);
    const html = await response.text();

    // Check for CAPTCHA
    if (html.includes('cf-turnstile') || response.status === 403) {
      return this.solveAndRetry(url, flightNumber, html);
    }

    return this.parseStatus(html);
  }

  async solveAndRetry(url, flightNumber, html) {
    const siteKeyMatch = html.match(/data-sitekey="(0x[^"]+)"/);
    if (!siteKeyMatch) throw new Error('No sitekey found');

    const token = await this.solveTurnstile(siteKeyMatch[1], url);

    const response = await fetch(`${url}/status/${flightNumber}`, {
      method: 'POST',
      body: new URLSearchParams({ 'cf-turnstile-response': token })
    });

    return this.parseStatus(await response.text());
  }
}

Часовые пояса и международные рейсы

Для пользователей из России, Беларуси и Казахстана большинство отслеживаемых рейсов — международные: Москва, Стамбул, Дубай, Алматы находятся в разных часовых поясах, и порталы авиакомпаний обычно отдают время вылета в местном времени аэропорта, а не в UTC. Если сравнивать departure_time между проверками без нормализации в UTC, скрипт может решить, что статус рейса "изменился", хотя на самом деле сместился только формат отображения времени. Правило простое: приводите checked_at и все временные метки из ответа портала к UTC сразу при парсинге (datetime.fromisoformat(...).astimezone(timezone.utc)), и только потом сравнивайте историю проверок между собой — это устраняет ложные срабатывания алертов при смене летнего/зимнего времени в Европе или при работе с рейсами, пересекающими несколько часовых поясов.

Как выбрать интервал проверки, чтобы не спровоцировать CAPTCHA

Интервал проверки — главный рычаг, которым вы управляете:

Частота проверки Частота CAPTCHA Рекомендация
Каждую минуту Высокая (50–80%) Слишком агрессивно
Каждые 5 минут Умеренная (10–30%) Для критических рейсов
Каждые 15 минут Низкая (5–10%) Баланс для повседневного мониторинга
Каждые 30 минут Очень низкая (<5%) Для долгосрочного отслеживания
Раз в час Минимальная (<1%) CAPTCHA срабатывает редко

Оптимизация сессии: меньше CAPTCHA, стабильнее данные

Большинство лишних CAPTCHA — не результат защиты портала от парсинга как такового, а реакция на нестабильную сессию: новый User-Agent, новый IP или потерянный cookie при каждом запросе выглядят для Cloudflare как новый посетитель. Четыре вещи стабилизируют сессию:

  • Сохраняйте cookie между проверками. Cloudflare cf_clearance действителен 15–30 минут — переиспользование requests.Session() избавляет от повторного challenge на каждый опрос.
  • Используйте один и тот же User-Agent на всём протяжении мониторинга рейса. Смена UA между запросами Cloudflare трактует как признак бота и запускает новую проверку.
  • Держите прокси постоянным для одного рейса или клиента. Один и тот же IP на протяжении сессии снижает подозрительность сильнее, чем ротация прокси на каждый запрос.
  • Распределяйте запросы во времени, а не отправляйте их пачками. Всплеск из 10–20 проверок за секунду провоцирует ограничение частоты быстрее, чем то же количество запросов, растянутое на минуту.

Типичные проблемы при мониторинге и как их исправить

Если мониторинг рейса начал сбоить, почти всегда причина — одна из четырёх:

  1. CAPTCHA появляется на каждой проверке. Причина — сессия не сохраняется между запросами. Переиспользуйте requests.Session() вместо создания нового соединения на каждый опрос.
  2. Ошибка 1020 (блокировка Cloudflare). Причина — слишком много запросов с одного IP за короткое время. Увеличьте интервал проверки или разнесите нагрузку по нескольким прокси.
  3. Данные устарели сразу после решения CAPTCHA. Причина — токен истёк за время между решением и фактическим запросом. Решайте CAPTCHA just-in-time, непосредственно перед запросом, а не заранее.
  4. Данные отличаются от того, что видно в браузере. Причина — ответ не рендерит JavaScript, а портал подгружает статус динамически. Используйте браузерную автоматизацию для таких сайтов вместо прямых HTTP-запросов.

Частые вопросы

Как часто проверять статус рейса, чтобы не словить блокировку по IP?

5–15 минут: реже CAPTCHA почти не появляется, раз в минуту — уже агрессивно.

Сколько потоков CaptchaAI нужно для 50 рейсов одновременно?

При интервале 15 минут и доле CAPTCHA 5–10% обычно хватает STANDARD ($30/мес, 15 потоков).

Почему после решения Turnstile страница отдаёт старые данные?

Токен истёк между решением и запросом. Решайте CAPTCHA непосредственно перед запросом, не кешируйте токен между проверками.

Мониторинг рейсов отечественных авиакомпаний работает так же, как международных?

Да, тот же принцип: любой портал, который часто опрашивают с одного диапазона IP, рано или поздно включает CAPTCHA или ограничение частоты запросов — это не специфика конкретных авиакомпаний. Стабильная сессия и разумный интервал проверки (см. раздел «Оптимизация сессии») снижают долю блокировок сильнее, чем смена прокси.

Следующие шаги

Комментарии для этой статьи отключены.