Casos de Uso

Manejo de CAPTCHA para monitorear el estado del vuelo

Los portales de vuelos muestran un CAPTCHA —casi siempre Cloudflare Turnstile— en cuanto detectan consultas automáticas repetidas, y ese es el único obstáculo real para un monitor de retrasos fiable. La solución es detectar el desafío, resolverlo con la API de CaptchaAI y reintentar con el token, sin intervención manual. Verás dónde aparecen y cómo integrarlos en un monitor en Python y JavaScript.

Dónde aparecen los CAPTCHA en los portales de vuelos

Una agencia en Latinoamérica que vigila salidas en Iberia, LATAM, Avianca o Aeroméxico verá desafíos distintos según la página: la portada suele llevar Turnstile y los buscadores usan reCAPTCHA.

Tipo de portal CAPTCHA Qué lo activa
Página de estado de vuelo de la aerolínea Cloudflare Turnstile Solicitudes frecuentes desde la misma IP
Paneles de llegadas y salidas del aeropuerto Cloudflare Challenge Detección de bots
Buscadores de vuelos reCAPTCHA v2/v3 Envío del formulario de búsqueda
Consulta del estado de la reserva reCAPTCHA v2 Antes de mostrar el itinerario
Páginas de límite de solicitudes de la API CAPTCHA personalizado Al superar los límites de solicitudes

Frecuencia de comprobación y aparición de CAPTCHA

Cuanto más seguido consultas, más CAPTCHA provocas. Para casi todos los vuelos, un intervalo de 5 a 15 minutos es el punto ideal.

Frecuencia de comprobación Tasa de CAPTCHA típica Recomendación
Cada 1 minuto Alta (50–80%) Demasiado agresivo: aumenta el intervalo
Cada 5 minutos Moderada (10–30%) Aceptable para vuelos críticos
Cada 15 minutos Baja (5–10%) Buen equilibrio para el seguimiento de rutina
Cada 30 minutos Muy baja (<5%) Ideal para el seguimiento a largo plazo
Cada hora Mínima (<1%) Los CAPTCHA rara vez se activan

Cómo reducir los CAPTCHA reutilizando la sesión

La palanca más eficaz no es resolver más rápido, sino disparar menos desafíos: una sesión estable reduce mucho la frecuencia del CAPTCHA.

Técnica Efecto
Conservar las cookies entre comprobaciones Mantiene válido el qa_validation_cookie de Cloudflare durante 15 a 30 minutos
Usar un User-Agent constante Cambiar la UA dispara nuevos desafíos
Mantener el mismo proxy La misma IP reduce las sospechas
Espaciar las solicitudes de forma uniforme Los patrones de ráfaga activan límites de solicitudes

Arquitectura de un monitor de estado de vuelos en Python

La base es una clase que reutiliza una sesión, detecta la página de CAPTCHA y resuelve Turnstile o reCAPTCHA antes de reintentar. El campo del token cambia según el tipo.

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()
        }

Comprobaciones periódicas con resolución automática de CAPTCHA

El bucle solo consulta el vuelo cada N segundos, guarda el historial y avisa al cambiar el estado; el CAPTCHA queda encapsulado en la clase anterior.

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")

Seguimiento de varias aerolíneas con JavaScript

Para vigilar operadores distintos a la vez, mantén un mapa de vuelos y resuelve cada CAPTCHA por separado. Este equivalente en JavaScript resuelve Turnstile y reintenta.

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());
  }
}

Resolución de problemas frecuentes

Problema Causa Solución
CAPTCHA en cada comprobación La sesión no se conservó Reutiliza requests.Session() entre comprobaciones
Bloqueo de Cloudflare (Error 1020) Demasiadas solicitudes Aumenta el intervalo de comprobación
Datos de vuelo desactualizados tras el CAPTCHA El token expiró durante la resolución Resuelve justo a tiempo (just-in-time)
Datos distintos a los que muestra el navegador Falta renderizado de JavaScript Usa automatización de navegador en sitios con mucho JS

Preguntas frecuentes

¿Qué plan de CaptchaAI necesito para monitorear vuelos?

Para la mayoría basta con el plan BASIC ($15/mes, 5 threads), porque cada comprobación resuelve un solo CAPTCHA a la vez. Si vigilas decenas de vuelos en paralelo, el plan STANDARD ($30/mes, 15 threads) da más margen. CaptchaAI factura por thread con resoluciones ilimitadas: el costo mensual en USD es predecible.

Depende de cada portal. Consulta el estado solo en páginas públicas y respeta los términos de servicio y la normativa de protección de datos aplicable (por ejemplo, GDPR en la UE o la LFPDPPP en México). Espaciar las solicitudes mantiene tu monitor dentro de un uso razonable.

¿Reutilizar la sesión reduce de verdad los CAPTCHA?

Sí, y es la medida más efectiva. Al conservar las cookies y mantener el mismo User-Agent y proxy, Cloudflare te reconoce como sesión estable. Sin persistencia, es habitual ver un CAPTCHA en casi cada comprobación.

¿Qué hago si Cloudflare me devuelve el Error 1020?

El Error 1020 significa que Cloudflare bloqueó la solicitud por reglas de firewall, casi siempre por un ritmo demasiado alto. Turnstile se resuelve en menos de 10 segundos con alta tasa de éxito, así que el problema no es la resolución: aumenta el intervalo, reutiliza la sesión y distribuye las solicitudes.

Artículos relacionados

Próximos pasos

Construye un monitor de vuelos fiable: obtén tu clave API de CaptchaAI y resuelve los CAPTCHA de las aerolíneas automáticamente.

Los comentarios están deshabilitados para este artículo.