Use Cases

Tratamento CAPTCHA para monitoramento de status de voo

Quem monitora status de voo por scraping ou API não documentada esbarra cedo ou tarde em um Cloudflare Turnstile, reCAPTCHA ou CAPTCHA personalizado — normalmente depois da quinta ou sexta consulta ao mesmo IP. A saída não é evitar o desafio, e sim tratá-lo como parte do fluxo: resolva automaticamente com uma API dedicada e ajuste o ritmo das verificações. Este guia mostra onde companhias aéreas e aeroportos colocam CAPTCHAs, como resolvê-los em Python e JavaScript com a CaptchaAI, e como equilibrar frequência de verificação contra taxa de CAPTCHA.

Onde os CAPTCHAs aparecem no monitoramento de voos

Antes de programar qualquer coisa, vale mapear onde cada tipo de portal costuma colocar a verificação:

Tipo de portal CAPTCHA Gatilho
Página de status de voo da companhia aérea Cloudflare Turnstile Requisições frequentes do mesmo IP
Painéis de chegada e partida do aeroporto Cloudflare Turnstile Detecção de bots
Buscadores de passagens aéreas reCAPTCHA v2/v3 Envio do formulário de busca
Verificação do status da reserva reCAPTCHA v2 Antes de exibir o itinerário
Páginas com limite de requisições da API CAPTCHA personalizado Após exceder os limites de requisição

Quanto monitorar sem disparar CAPTCHA em excesso

A frequência de verificação é o fator que mais influencia quantos CAPTCHAs você vai enfrentar. Intervalos mais curtos trazem dados mais frescos, mas multiplicam os desafios — vale decidir isso antes de escrever o código do monitor:

Frequência de verificação Taxa típica de CAPTCHA Recomendação
A cada 1 minuto Alta (50–80%) Agressivo demais — aumente o intervalo
A cada 5 minutos Moderada (10–30%) Aceitável para voos críticos
A cada 15 minutos Baixa (5–10%) Bom equilíbrio para monitoramento de rotina
A cada 30 minutos Muito baixa (<5%) Ideal para rastreamento de longo prazo
A cada hora Mínima (<1%) CAPTCHAs raramente aparecem

Como construir o monitor de voos em Python

A classe abaixo faz o trabalho pesado: consulta a página de status, detecta se a resposta é uma página de CAPTCHA (pelo HTTP 403 ou pela presença de cf-turnstile/g-recaptcha no HTML), identifica o tipo, resolve via in.php/res.php da CaptchaAI e reenvia a requisição com o token no campo correto (cf-turnstile-response ou g-recaptcha-response). O resultado é reaproveitável para qualquer companhia aérea que use Turnstile ou reCAPTCHA nesse padrão:

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

Uma chamada isolada resolve um CAPTCHA pontual. Para monitoramento de verdade, rode essa checagem em loop e alerte quando o status mudar.

Verificações periódicas com resolução automática de CAPTCHA

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

O interval_seconds=300 acima é um ponto de partida seguro — ajuste com base na tabela de frequência vista antes.

Como reduzir a frequência de CAPTCHAs otimizando a sessão

Reduzir quantas vezes o CAPTCHA aparece é mais barato do que resolver mais desafios. Estas quatro práticas cortam boa parte dos disparos repetidos:

Técnica Efeito
Persistir cookies entre verificações Reduz novos desafios do Cloudflare Turnstile por 15 a 30 minutos
Usar um User-Agent consistente Trocar a UA aciona novos desafios
Manter o mesmo proxy O mesmo IP reduz a suspeita
Espaçar as requisições de forma uniforme Padrões em rajada acionam limites de requisição

Monitorando várias companhias aéreas em JavaScript

Para acompanhar várias companhias ao mesmo tempo, mantenha uma sessão e um histórico por voo — cada uma resolve o próprio CAPTCHA de forma independente, sem travar as demais:

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

Considerações para quem opera a partir do Brasil

Se os workers rodarem em sa-east-1 (São Paulo) da AWS e os portais monitorados estiverem nos EUA ou na Europa, conte com 150–250 ms de RTT só de rede, antes da resolução do CAPTCHA em si. Isso não muda o comportamento do Turnstile ou do reCAPTCHA, mas afeta o cronômetro do monitoramento — prefira as janelas de 5 a 15 minutos da tabela acima em vez de intervalos pensados para infraestrutura mais próxima do portal de origem.

Se o seu pipeline também grava dados de passageiro (nome, localizador de reserva, CPF) capturados na página de status, trate esse armazenamento como dado pessoal sob a LGPD: retenção curta, sem logs persistentes desnecessários.

Solução de problemas comuns

Problema Causa Correção
CAPTCHA em toda verificação Sessão não persistida Reutilize requests.Session() entre as checagens
Bloqueio da Cloudflare (erro 1020) Requisições em excesso Aumente o intervalo de verificação
Dados de voo desatualizados após o CAPTCHA Token expirou durante a resolução Resolva o CAPTCHA just-in-time, próximo do envio
Dados diferentes dos exibidos no navegador Falta de renderização de JavaScript Use automação de navegador para sites com uso pesado de JS

Perguntas frequentes

Com que frequência posso verificar sem tomar bloqueio de IP?

Entre 5 e 15 minutos costuma ser seguro para a maioria dos portais. Abaixo disso, a taxa de CAPTCHA sobe rápido e o limite passa a ser o rate limit do portal, não a resolução do desafio — a CaptchaAI resolve o Turnstile normalmente em menos de 10 segundos, com alta taxa de sucesso nos tipos suportados.

O CaptchaAI resolve os desafios em tempo real, sem atrasar a checagem?

Sim. O Turnstile normalmente é resolvido em menos de 10 segundos e o reCAPTCHA v2 em menos de 60 segundos — dá para embutir a resolução no próprio loop de verificação sem trocar a arquitetura do monitor.

Preciso de um navegador completo (Selenium, Playwright) ou a API HTTP resolve?

Para a maioria dos portais de status, requisições HTTP diretas com resolução via API bastam. Só vale automação de navegador quando os dados só aparecem depois da renderização de JavaScript no cliente — é o caso descrito na tabela de solução de problemas acima.

Rodar o monitor a partir do Brasil aumenta a chance de CAPTCHA?

Não diretamente — o gatilho é volume e padrão de requisições, não a origem geográfica. Mas a latência extra de rodar em sa-east-1 contra portais nos EUA/Europa (ver seção acima) pode fazer um monitoramento no limite perder a janela do token, então dê uma folga maior no timeout.

O token do Turnstile resolvido serve para mais de uma verificação?

Não. Cada token vale para um único envio ao portal de destino. Resolva um novo token a cada checagem que encontrar CAPTCHA — tentar reaproveitar um token expirado é a causa mais comum do problema "dados desatualizados" na tabela de solução de problemas.

Artigos relacionados

Próximas etapas

Monte o monitoramento sem interrupções por CAPTCHA: obtenha sua chave de API da CaptchaAI, plugue o exemplo em Python ou JavaScript acima e ajuste o intervalo de verificação para o seu volume de voos.

Os comentários estão desativados para este artigo.