Use Cases

Penanganan CAPTCHA untuk Monitoring Status Penerbangan

Cek status penerbangan lebih dari beberapa kali per jam dari IP yang sama, dan Turnstile atau reCAPTCHA hampir pasti muncul di portal maskapai. Menurunkan frekuensi cek bukan solusi kalau klien butuh update mendekati real-time — yang dibutuhkan adalah monitor yang menyelesaikan CAPTCHA itu sendiri secara otomatis, setiap kali muncul, tanpa menunggu orang buka browser. Panduan ini membangun flight tracker Python dan JavaScript yang menangani Turnstile, reCAPTCHA, dan CAPTCHA kustom on-the-fly, pola yang sama dipakai agensi travel korporat maupun tim data yang memantau banyak penerbangan untuk klien bisnis.

Kapan Turnstile dan reCAPTCHA Muncul di Portal Maskapai

Lima titik yang paling sering memunculkan CAPTCHA saat memantau penerbangan:

  • Halaman status penerbangan maskapai — Cloudflare Turnstile, dipicu request berulang dari IP yang sama.
  • Board kedatangan/keberangkatan bandara — Cloudflare Challenge, saat trafik terdeteksi sebagai bot.
  • Search engine penerbangan — reCAPTCHA v2/v3, dipicu submit form pencarian berkali-kali.
  • Cek status pemesanan — reCAPTCHA v2, muncul sebelum itinerary ditampilkan.
  • Halaman rate limit API — CAPTCHA kustom, muncul setelah melewati batas request.

Menentukan Interval Cek yang Aman Sebelum Membangun Monitor

Sebelum menulis kode, tentukan dulu seberapa sering monitor perlu polling — ini yang paling menentukan seberapa sering CAPTCHA muncul, bukan teknik solving-nya:

Angka tingkat CAPTCHA di bawah ini berdasarkan pengamatan lapangan pada portal maskapai umum, bukan angka resmi dari satu maskapai tertentu — hasil bisa berbeda tergantung portal dan waktu pengujian.

Frekuensi cek Tingkat CAPTCHA tipikal Rekomendasi
Setiap 1 menit Tinggi (50–80%) Terlalu agresif — perbesar interval
Setiap 5 menit Sedang (10–30%) Wajar untuk penerbangan yang benar-benar kritis
Setiap 15 menit Rendah (5–10%) Titik seimbang untuk monitoring rutin
Setiap 30 menit Sangat rendah (<5%) Cocok untuk pelacakan jangka panjang
Setiap jam Minimal (<1%) CAPTCHA nyaris tidak pernah terpicu

Membangun FlightMonitor: Arsitektur Dasar

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

Loop Monitoring Berkala dengan Penanganan CAPTCHA Otomatis

Setelah class dasar berjalan, bungkus dengan loop yang mengecek ulang tiap interval dan mencatat riwayat status setiap penerbangan.

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

Memantau Banyak Maskapai Sekaligus dengan JavaScript

Kalau Anda memantau beberapa maskapai untuk satu klien korporat, pola berikut menjaga tiap maskapai punya sesi dan riwayatnya sendiri — bukan satu monitor besar yang gagal total kalau satu portal berubah struktur.

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

Menjaga Sesi Supaya CAPTCHA Tidak Terus Muncul

Server maskapai lebih curiga pada koneksi yang selalu "baru" daripada pada frekuensi cek itu sendiri. Empat kebiasaan berikut menekan jumlah CAPTCHA yang harus diselesaikan:

  • Pertahankan cookie antar cek. Cookie validasi Cloudflare (qa_validation_cookie) biasanya berlaku 15–30 menit — reuse selama masih valid, jangan mulai sesi baru tiap kali.
  • Gunakan User-Agent yang konsisten. Mengganti-ganti UA di setiap request adalah salah satu pemicu challenge baru yang paling sering diabaikan.
  • Jaga konsistensi proxy/IP. IP yang sama secara konsisten terlihat lebih "manusiawi" bagi Cloudflare dan Turnstile dibanding IP yang berpindah-pindah.
  • Sebar request secara merata, bukan burst. Pola cek serentak untuk banyak penerbangan sekaligus lebih mudah memicu rate limit daripada yang dijadwalkan berjeda.

Troubleshooting: Masalah Umum dan Solusinya

Empat masalah yang paling sering dilaporkan tim yang menjalankan monitor seperti ini, beserta penyebab dan perbaikannya:

  • CAPTCHA muncul di setiap cek — biasanya karena sesi tidak dipertahankan antar request. Perbaikan: reuse satu requests.Session() untuk semua pengecekan, jangan buat sesi baru tiap kali.
  • Cloudflare block (Error 1020) — request datang terlalu sering dari IP yang sama. Perbaikan: perbesar interval cek atau distribusikan lewat lebih dari satu egress.
  • Data penerbangan terasa basi setelah CAPTCHA diselesaikan — token kedaluwarsa selama proses solving berlangsung. Perbaikan: pakai pola just-in-time solving, bukan solve di awal lalu simpan token lama.
  • Data berbeda dari yang tampil di browser — halaman butuh JavaScript rendering yang tidak dijalankan monitor. Perbaikan: pakai browser automation (Playwright/Puppeteer) untuk situs JS-heavy.

Pertanyaan Umum

Berapa interval polling yang ideal supaya CAPTCHA tidak terus-menerus muncul?

Mulai dari 15 menit untuk monitoring rutin, turunkan ke 5 menit hanya untuk penerbangan yang statusnya kritis dalam beberapa jam ke depan. Di bawah itu, rate limit portal — bukan proses solving — yang jadi penghambat utama.

Apakah reCAPTCHA v3 di mesin pencari tiket penerbangan bisa diselesaikan otomatis?

Bisa. reCAPTCHA v3 didukung penuh oleh CaptchaAI dengan tingkat keberhasilan tinggi, dan biasanya selesai dalam hitungan detik karena berjalan tanpa interaksi pengguna — cukup kirim googlekey dan pageurl ke in.php seperti contoh arsitektur di atas.

Bagaimana menangani CAPTCHA kustom di halaman rate-limit API maskapai?

Umumnya berbasis gambar sederhana dan bisa dikirim lewat metode Normal/Image (OCR) CaptchaAI. Kalau ternyata varian reCAPTCHA, Turnstile, atau GeeTest v3 yang dikemas ulang, gunakan metode solve sesuai tipe aslinya.

Bagaimana menjaga kepatuhan data saat menyimpan hasil monitoring penerbangan?

Data status penerbangan (jadwal, gate, delay) umumnya bukan data pribadi. Tapi begitu dikaitkan dengan nama penumpang atau nomor pemesanan klien, UU Pelindungan Data Pribadi (UU 27/2022) berlaku — simpan seperlunya dan batasi akses.

Artikel Terkait


Langkah Selanjutnya

Monitor status penerbangan seharusnya jalan tanpa perlu dijaga manual tiap kali CAPTCHA muncul — dapatkan API key CaptchaAI Anda dan biarkan Turnstile, reCAPTCHA, serta CAPTCHA kustom diselesaikan otomatis di background.

Komentar dinonaktifkan untuk artikel ini.