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
- Comparação entre GeeTest e Cloudflare Turnstile
- Cloudflare Turnstile 403 após correção do token
- Modos de widget do Cloudflare Turnstile explicados
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.