Anwendungsfälle

CAPTCHA-Behandlung zur Flugstatusüberwachung

Flugstatus-Portale beantworten wiederholte automatisierte Abfragen mit einer CAPTCHA-Abfrage: Cloudflare Turnstile, Cloudflare Challenge oder reCAPTCHA schieben sich zwischen Ihren Scraper und die Echtzeitdaten zu Verspätungen, Gate-Wechseln und Annullierungen. Die Antwort darauf ist kein aggressiveres Polling, sondern ein Monitoring-Client, der die Abfrage erkennt, das Token über CaptchaAI löst und den Abruf in derselben Sitzung fortsetzt. Dieser Leitfaden zeigt genau diese Architektur – von der Erkennung über das Lösen bis zur Intervallsteuerung, die gar nicht erst unnötig viele CAPTCHAs auslöst.

Wo CAPTCHAs auf Flug- und Flughafenportalen auftauchen

Bevor Sie Code schreiben, lohnt sich der Überblick, an welcher Stelle im Abruf die Abfrage überhaupt erscheint. Das bestimmt, welchen CAPTCHA-Typ Sie lösen müssen und mit welchem Feldnamen Sie das Token zurückschicken.

Portaltyp CAPTCHA Auslöser
Flugstatusseite der Fluggesellschaft Cloudflare Turnstile Häufige Anfragen von derselben IP
Ankunfts-/Abflugtafeln der Flughäfen Cloudflare Challenge Bot-Erkennung
Flugsuchmaschinen reCAPTCHA v2/v3 Übermittlung des Suchformulars
Buchungsstatus-Prüfung reCAPTCHA v2 Vor Anzeige der Reiseroute
API-Rate-Limit-Seiten Benutzerdefiniertes CAPTCHA Nach Überschreiten der Anfragelimits

Ein typisches DACH-Szenario: Sie verfolgen einen Eurowings-Flug ab Düsseldorf oder lesen die Abflugtafel am Flughafen München (MUC) aus. Nach einigen schnellen Aufrufen von derselben IP liefert das Portal statt der Statusseite eine Turnstile-Abfrage aus. Ein Monitoring-Client muss diesen Fall als Normalzustand behandeln, nicht als Fehler. Beachten Sie dabei die Nutzungsbedingungen der jeweiligen Quelle und ein angemessenes Intervall – öffentlich einsehbare Flugdaten abzufragen ist etwas anderes, als ein Portal mit Anfragen zu überlasten.

Flugstatus-Monitoring: die Architektur

Der Kern ist eine Klasse, die eine requests.Session() wiederverwendet, jede Antwort auf eine CAPTCHA-Seite prüft und bei Bedarf löst. Entscheidend ist die Erkennung: Ein Status 403 oder die Marker cf-turnstile bzw. g-recaptcha im HTML signalisieren, dass zuerst ein Token benötigt wird. Erst danach parst der Client die Flugdaten.

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

Turnstile nutzt den Feldnamen cf-turnstile-response, reCAPTCHA g-recaptcha-response – die Zuordnung im Code entscheidet, ob der erneute POST akzeptiert wird. Das Polling fragt das Ergebnis in Abständen ab und bricht nach einem Timeout kontrolliert ab, statt endlos zu blockieren.

Regelmäßige Flugüberwachung mit CAPTCHA-Handling

Für die eigentliche Überwachung läuft check_flight in einer Schleife mit festem Intervall. Der Client führt eine Historie, meldet Statuswechsel und fängt Fehler pro Durchlauf ab, damit ein einzelner Fehlschlag die gesamte Überwachung nicht abbricht.

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

Mehrere Fluggesellschaften parallel überwachen (JavaScript)

Wer mehrere Flüge über verschiedene Airlines hinweg verfolgt, hält am besten eine Map pro Flugnummer und löst die Turnstile-Abfrage für jede Quelle unabhängig. So bleibt eine blockierte Airline ohne Folgen für die übrigen.

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

Prüfintervall und CAPTCHA-Rate im Verhältnis

Die wichtigste Stellschraube ist das Intervall: Je kürzer der Abstand, desto häufiger die Abfrage. Die folgenden Richtwerte helfen, eine Balance zwischen Aktualität und Aufwand zu finden.

Prüfintervall Typische CAPTCHA-Rate Empfehlung
Jede Minute Hoch (50–80 %) Zu aggressiv – Intervall verlängern
Alle 5 Minuten Mäßig (10–30 %) Für kritische Flüge akzeptabel
Alle 15 Minuten Niedrig (5–10 %) Gute Balance für die Routineüberwachung
Alle 30 Minuten Sehr niedrig (< 5 %) Am besten für die Langzeitverfolgung
Stündlich Minimal (< 1 %) CAPTCHAs werden selten ausgelöst

Für einen Flug am Reisetag sind 5 Minuten sinnvoll, für die Beobachtung über mehrere Tage reichen 30 Minuten. In der Praxis ist nicht das Lösen der Engpass, sondern das Rate-Limiting der Quelle.

CAPTCHA-Begegnungen über die Sitzung reduzieren

Sie senken die Zahl der Abfragen deutlich, indem Sie den Sitzungsstatus konsequent beibehalten. Cloudflare vergibt nach einer gelösten Abfrage ein cf_clearance-Cookie, das kurzzeitig weitere Abfragen erspart.

Technik Wirkung
Cookies zwischen den Prüfungen beibehalten Cloudflare cf_clearance bleibt 15–30 Minuten gültig
Konsistenten User-Agent verwenden Ein wechselnder User-Agent löst neue Abfragen aus
Proxy-Konsistenz wahren Gleiche IP sorgt für stabile Cloudflare-Sitzungen
Anfragen gleichmäßig verteilen Burst-Muster lösen Rate-Limiting aus

Ein Hinweis zum Datenschutz: Sobald Sie mit Proxys arbeiten, verarbeiten Sie IP-Adressen – nach DSGVO personenbezogene Daten. Prüfen Sie Ihre Rechtsgrundlage und Datenflüsse, wenn Sie Flugdaten in größerem Umfang erheben.

Kosten: Thread-basierte Abrechnung statt Preis pro Lösung

CaptchaAI rechnet pro gleichzeitigem Thread ab, nicht pro gelöster CAPTCHA – jeder Tarif enthält unbegrenzte Lösungen pro Thread. Für die Überwachung einzelner Flüge genügt der BASIC-Tarif (15 $/Monat, 5 Threads); wer viele Airlines parallel abfragt, wählt STANDARD (30 $/Monat, 15 Threads) oder ADVANCE (90 $/Monat, 50 Threads). Preise verstehen sich in US-Dollar. Ein einzelner Monitoring-Prozess braucht selten mehr als einen aktiven Thread gleichzeitig, weil zwischen den Abfragen längere Pausen liegen.

Fehlerbehebung

Problem Ursache Lösung
Zu viele CAPTCHAs in kurzer Zeit Abrufrate oder Parallelität ist für die Quelle zu aggressiv Verlängern Sie das Intervall, halten Sie die Sitzung stabil und prüfen Sie die Qualität Ihrer Proxys
Daten fehlen trotz gelöster CAPTCHA Der Parser liest eine alte oder unvollständige Ansicht aus Extrahieren Sie Daten erst nach erfolgreicher Token-Anwendung in derselben Sitzung
Cloudflare-Sperre (Fehler 1020) Zu viele Anfragen in zu kurzer Zeit Erhöhen Sie das Prüfintervall und verteilen Sie die Aufrufe gleichmäßig
Kosten steigen stärker als erwartet Zu viele Wiederholungen oder unnötige Seitenaufrufe lösen zusätzliche Abfragen aus Lösen Sie nur kritische Schritte und protokollieren Sie Wiederholungen pro Quelle

Häufige Fragen

Welche CAPTCHA-Typen tauchen bei Flugportalen am häufigsten auf?

Meist Cloudflare Turnstile auf Statusseiten und Cloudflare Challenge auf Flughafentafeln; Flugsuchmaschinen setzen zusätzlich reCAPTCHA v2/v3 ein. CaptchaAI löst alle diese Typen mit hoher Erfolgsquote – entscheidend ist, dass Ihr Code den richtigen Feldnamen zurückschickt.

Kostet jede gelöste CAPTCHA bei CaptchaAI extra?

Nein. Die Abrechnung erfolgt Thread-basiert, nicht pro Lösung – jeder Tarif enthält unbegrenzte Lösungen pro Thread. Ein Monitoring-Job mit einem aktiven Thread bleibt damit auch bei vielen Abfragen im Rahmen des BASIC-Tarifs (15 $/Monat).

Was passiert, wenn das Turnstile-Token vor dem Datenabruf abläuft?

Turnstile-Token sind nur kurz gültig. Lösen Sie die Abfrage deshalb direkt vor dem POST und verwenden Sie das Token sofort in derselben Sitzung. Läuft es dennoch ab, erkennt der Client die erneute CAPTCHA-Seite und löst automatisch nach.

Darf ich Flugdaten überhaupt automatisiert abfragen?

Öffentlich einsehbare Flugstatusdaten abzurufen ist grundsätzlich legitim, doch prüfen Sie die Nutzungsbedingungen der Quelle und halten Sie ein rücksichtsvolles Intervall ein. Sobald Proxys und damit IP-Adressen ins Spiel kommen, gilt zusätzlich die DSGVO – dokumentieren Sie Ihre Rechtsgrundlage.


Verwandte Leitfäden

Kommentare sind für diesen Artikel deaktiviert.