Monitoring status penerbangan memerlukan pengecekan rutin di beberapa portal maskapai dan bandara. Portal ini melindungi data real-time mereka dengan Cloudflare Turnstile, reCAPTCHA, dan CAPTCHA kustom — terutama saat mendeteksi query otomatis yang berulang. Berikut cara menangani CAPTCHA sekaligus membangun alat pelacak penerbangan yang andal.
Di Mana CAPTCHA Muncul
| Tipe Portal | CAPTCHA | Pemicu |
|---|---|---|
| Halaman status penerbangan maskapai | Cloudflare Turnstile | Request sering dari IP yang sama |
| Board kedatangan/keberangkatan bandara | Cloudflare Challenge | Deteksi bot |
| Search engine penerbangan | reCAPTCHA v2/v3 | Submit form pencarian |
| Cek status pemesanan | reCAPTCHA v2 | Sebelum menampilkan itinerary |
| Halaman rate limit API | CAPTCHA kustom | Setelah melampaui batas request |
Arsitektur Monitor Penerbangan
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()
}
Monitoring Berkala dengan Penanganan 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")
Monitor Multi-Maskapai (JavaScript)
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());
}
}
Frekuensi Monitoring dan Tingkat CAPTCHA
| Frekuensi cek | Tingkat CAPTCHA tipikal | Rekomendasi |
|---|---|---|
| Setiap 1 menit | Tinggi (50–80%) | Terlalu agresif — tambah interval |
| Setiap 5 menit | Sedang (10–30%) | Dapat diterima untuk penerbangan penting |
| Setiap 15 menit | Rendah (5–10%) | Keseimbangan baik untuk monitoring rutin |
| Setiap 30 menit | Sangat rendah (<5%) | Terbaik untuk pelacakan jangka panjang |
| Setiap jam | Minimal (<1%) | CAPTCHA jarang terpicu |
Optimasi Sesi
Kurangi frekuensi CAPTCHA dengan mempertahankan state sesi:
| Teknik | Efek |
|---|---|
| Pertahankan cookie antar cek | Cloudflare qa_validation_cookie berlaku 15–30 menit |
| Gunakan User-Agent yang konsisten | Mengubah UA memicu challenge baru |
| Pertahankan konsistensi proxy | IP yang sama mengurangi kecurigaan |
| Distribusikan request secara merata | Pola burst memicu rate limit |
Pemecahan Masalah
| Masalah | Penyebab | Perbaikan |
|---|---|---|
| CAPTCHA di setiap cek | Sesi tidak dilanjutkan | Reuse requests.Session() di semua pengecekan |
| Cloudflare block (Error 1020) | Terlalu banyak request | Tingkatkan interval cek |
| Data penerbangan basi setelah CAPTCHA | Token kedaluwarsa saat solving | Gunakan just-in-time solving |
| Data berbeda dari yang ditampilkan browser | JavaScript rendering tidak ada | Gunakan browser automation untuk situs JS-heavy |
Pertanyaan Umum
Seberapa sering saya harus mengecek status penerbangan?
Biasanya setiap 5–15 menit. Pengecekan lebih sering memicu lebih banyak CAPTCHA dan dapat menyebabkan pemblokiran IP. CaptchaAI menangani Turnstile dengan success rate tinggi, jadi faktor pembatasnya adalah rate limit portal, bukan solve CAPTCHA.
Bisakah saya memonitor penerbangan dari beberapa maskapai sekaligus?
Ya. Gunakan sesi terpisah per maskapai dan solve CAPTCHA secara independen untuk masing-masing. CaptchaAI menangani concurrent request di berbagai situs.
Apakah mobile API maskapai memiliki CAPTCHA?
Mobile API biasanya menggunakan autentikasi yang berbeda (API key, OAuth) dan bukan CAPTCHA. Namun, endpoint web yang mereka sajikan mungkin masih memiliki proteksi Cloudflare.
Artikel Terkait
- Cloudflare Turnstile 403 Setelah Token Fix
- Mode Widget Cloudflare Turnstile Explained
Langkah Selanjutnya
Bangun monitoring penerbangan yang andal — dapatkan API key CaptchaAI Anda dan tangani CAPTCHA maskapai secara otomatis.