Anwendungsfälle

Überwachung der Lieferkette mit CAPTCHA-Handhabung

Eine Monitoring-Pipeline für die Lieferkette scheitert selten an der Datenextraktion – sie scheitert an dem reCAPTCHA v2, das ein Lieferantenportal vor die Lagerbestände schaltet. Die kurze Antwort: Solche Abfragen lassen sich per API lösen, sodass die Erfassung ohne manuelles Eingreifen weiterläuft. CaptchaAI übernimmt reCAPTCHA v2, Cloudflare Turnstile und Bild-CAPTCHAs; Ihr Scraper füllt weiter Bestände, Preise und Lieferzeiten in Ihr Dashboard.

Die eigentliche Qualitätsfrage lautet dabei nicht, ob sich ein CAPTCHA lösen lässt, sondern welche Quelle welche Priorität verdient. In Just-in-Time-Ketten, wie sie die deutsche Automobil- und Maschinenbauindustrie fahren, entscheidet die Aktualität einer Bestandszahl über Bandstillstand oder Weiterlauf.

Wo CAPTCHAs Lieferkettendaten blockieren

Quelltyp CAPTCHA-Typ Daten Häufigkeit
Lieferantenportale reCAPTCHA v2 Lagerbestand, Preise, Lieferzeiten Täglich
Versandunternehmen Cloudflare Turnstile Sendungsverfolgung, Preise, voraussichtliche Liefertermine Stündlich
Herstellerkataloge Bild-CAPTCHA Produktspezifikationen, MOQs Wöchentlich
Zollportale reCAPTCHA v2 Zollsätze, Tarifcodes Täglich
Hafenbehörden Bild-CAPTCHA Schiffsfahrpläne, Überlastung im Hafen Alle 6 Stunden
Warenbörsen reCAPTCHA v3 Spotpreise, Futures Echtzeit

Quellen nach Kritikalität takten

Legen Sie die Kadenz nach Geschäftsrisiko fest, nicht nach technischer Machbarkeit – das hält die Zahl der CAPTCHAs niedrig und die Daten trotzdem frisch.

Quelltyp Typische Priorität Sinnvolle Kadenz
Kritische Lieferanten mit Lagerengpässen Hoch Engmaschig oder ereignisgetrieben
Versand- und Ratenquellen Mittel bis hoch Regelmäßig im Stundenbereich
Herstellerkataloge Mittel Täglich oder wöchentlich
Zoll- und Compliance-Portale Mittel Täglich oder bei relevanten Änderungen

Multi-Lieferanten-Monitor in Python

Der folgende Monitor prüft mehrere Lieferanten in einem Durchlauf und wählt je Portal automatisch die passende Lösungsroutine.

import requests
import time
import re
import json
import base64
from datetime import datetime

CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"


def solve_recaptcha(sitekey, pageurl):
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
        "key": CAPTCHAAI_KEY, "method": "userrecaptcha",
        "googlekey": sitekey, "pageurl": pageurl, "json": 1,
    })
    task_id = resp.json()["request"]
    for _ in range(60):
        time.sleep(5)
        result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY, "action": "get",
            "id": task_id, "json": 1,
        })
        data = result.json()
        if data["request"] != "CAPCHA_NOT_READY":
            return data["request"]
    raise TimeoutError("Timeout")


def solve_turnstile(sitekey, pageurl):
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
        "key": CAPTCHAAI_KEY, "method": "turnstile",
        "sitekey": sitekey, "pageurl": pageurl, "json": 1,
    })
    task_id = resp.json()["request"]
    for _ in range(60):
        time.sleep(5)
        result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY, "action": "get",
            "id": task_id, "json": 1,
        })
        data = result.json()
        if data["request"] != "CAPCHA_NOT_READY":
            return data["request"]
    raise TimeoutError("Timeout")


def solve_image(image_bytes):
    img_b64 = base64.b64encode(image_bytes).decode()
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
        "key": CAPTCHAAI_KEY, "method": "base64",
        "body": img_b64, "json": 1,
    })
    task_id = resp.json()["request"]
    for _ in range(20):
        time.sleep(3)
        result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY, "action": "get",
            "id": task_id, "json": 1,
        })
        data = result.json()
        if data["request"] != "CAPCHA_NOT_READY":
            return data["request"]
    raise TimeoutError("Timeout")


class SupplyChainMonitor:
    def __init__(self, suppliers, proxy=None):
        self.suppliers = suppliers
        self.session = requests.Session()
        if proxy:
            self.session.proxies = {"http": proxy, "https": proxy}
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
        })

    def check_all(self):
        """Check inventory and pricing across all suppliers."""
        report = {
            "timestamp": datetime.now().isoformat(),
            "suppliers": {},
        }

        for supplier in self.suppliers:
            try:
                data = self._check_supplier(supplier)
                report["suppliers"][supplier["name"]] = {
                    "status": "success",
                    "data": data,
                }
            except Exception as e:
                report["suppliers"][supplier["name"]] = {
                    "status": "error",
                    "error": str(e),
                }
            time.sleep(3)

        return report

    def _check_supplier(self, supplier):
        url = supplier["url"]
        resp = self.session.get(url, timeout=30)

        # Handle CAPTCHA based on type
        captcha_type = supplier.get("captcha_type")
        if captcha_type and self._has_captcha(resp.text):
            resp = self._solve_captcha(resp, url, supplier)

        from bs4 import BeautifulSoup
        soup = BeautifulSoup(resp.text, "html.parser")

        return {
            "products": self._extract_inventory(soup),
            "last_updated": self._extract_date(soup),
        }

    def _has_captcha(self, html):
        return any(tag in html.lower() for tag in [
            'data-sitekey', 'g-recaptcha', 'cf-turnstile', 'captcha',
        ])

    def _solve_captcha(self, resp, url, supplier):
        captcha_type = supplier.get("captcha_type", "recaptcha")
        sitekey = supplier.get("sitekey", "")

        if not sitekey:
            match = re.search(r'data-sitekey="([^"]+)"', resp.text)
            sitekey = match.group(1) if match else ""

        if captcha_type == "turnstile":
            token = solve_turnstile(sitekey, url)
            return self.session.post(url, data={"cf-turnstile-response": token})
        elif captcha_type == "image":
            match = re.search(r'src="(/captcha[^"]+)"', resp.text)
            if match:
                img_resp = self.session.get(url.rstrip("/") + match.group(1))
                answer = solve_image(img_resp.content)
                return self.session.post(url, data={"captcha": answer})
        else:
            token = solve_recaptcha(sitekey, url)
            return self.session.post(url, data={"g-recaptcha-response": token})

        return resp

    def _extract_inventory(self, soup):
        items = []
        for row in soup.select("table.inventory tr, .product-row"):
            cols = row.select("td, .col")
            if len(cols) >= 3:
                items.append({
                    "sku": cols[0].get_text(strip=True),
                    "stock": cols[1].get_text(strip=True),
                    "price": cols[2].get_text(strip=True),
                })
        return items

    def _extract_date(self, soup):
        date_el = soup.select_one(".last-updated, .update-time")
        return date_el.get_text(strip=True) if date_el else ""


# Configure suppliers
suppliers = [
    {
        "name": "Supplier A",
        "url": "https://supplier-a.example.com/inventory",
        "captcha_type": "recaptcha",
        "sitekey": "6Lc_xxxxxxx",
    },
    {
        "name": "Carrier B",
        "url": "https://carrier-b.example.com/rates",
        "captcha_type": "turnstile",
        "sitekey": "0x4AAAAAAA_xxx",
    },
    {
        "name": "Manufacturer C",
        "url": "https://manufacturer-c.example.com/catalog",
        "captcha_type": "image",
    },
]

monitor = SupplyChainMonitor(
    suppliers=suppliers,
    proxy="http://user:pass@residential.proxy.com:5000",
)
report = monitor.check_all()
print(json.dumps(report, indent=2))

Die Klasse trennt Erkennung und Lösung sauber: Zuerst prüft _has_captcha den Seitenquelltext auf typische Marker wie data-sitekey, und nur bei einem Treffer ruft _solve_captcha die zum Portal passende Routine auf. Fällt eine Quelle aus, hält check_all den Fehler fest und läuft weiter – ein einzelnes blockiertes Portal legt den gesamten Durchlauf also nicht lahm. Für belastbare Werte empfiehlt sich ein Residential-Proxy pro Zielregion, damit geobasierte Preise zum jeweiligen Einkaufsmarkt passen.

Für den Dauerbetrieb sind bei DACH-Teams Hetzner, IONOS oder netcup gängige VPS-Ziele, orchestriert über GitLab CI oder GitHub Actions.


Versandkosten über Turnstile-Portale abfragen

Carrier-Portale verstecken ihre Ratenrechner oft hinter Cloudflare Turnstile. Der folgende Tracker holt den Sitekey, lässt das Token lösen und sendet die Anfrage ab. Kommt statt der Raten eine leere Liste zurück, liegt das meist an einer Login-Pflicht oder einem abweichenden Formularfeld – prüfen Sie dann zuerst den tatsächlichen POST-Body im Netzwerk-Tab des Browsers.

class ShippingRateTracker:
    def __init__(self, proxy=None):
        self.session = requests.Session()
        if proxy:
            self.session.proxies = {"http": proxy, "https": proxy}
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
        })

    def get_rates(self, carrier_url, origin, destination, weight):
        """Fetch shipping rates, handling Turnstile CAPTCHA."""
        resp = self.session.get(carrier_url, timeout=30)

        sitekey_match = re.search(r'data-sitekey="([^"]+)"', resp.text)
        if sitekey_match:
            token = solve_turnstile(sitekey_match.group(1), carrier_url)
            resp = self.session.post(carrier_url, data={
                "origin": origin,
                "destination": destination,
                "weight": weight,
                "cf-turnstile-response": token,
            })

        if resp.status_code == 200:
            return resp.json().get("rates", [])
        return []

Bei Bestandsunterschreitung alarmieren

Diese Schleife vergleicht jeden Durchlauf mit dem vorherigen Stand und meldet, sobald ein SKU unter den Schwellenwert fällt. In der Praxis genügt selten ein print: Hängen Sie an die Alarmstelle eine Anbindung an Slack, Microsoft Teams oder Ihr Ticketsystem, damit der Einkauf sofort reagieren kann. Das check_interval richten Sie am Geschäftsrisiko aus – ein kritischer Engpassartikel rechtfertigt eine engere Taktung als ein selten bewegter Katalogposten.

def monitor_with_alerts(monitor, alert_thresholds, check_interval=3600):
    """Continuously monitor and alert on inventory changes."""
    previous_data = {}

    while True:
        report = monitor.check_all()

        for supplier, info in report["suppliers"].items():
            if info["status"] != "success":
                continue

            for product in info["data"].get("products", []):
                sku = product["sku"]
                stock = product.get("stock", "")

                # Parse stock level
                try:
                    stock_qty = int(re.sub(r'\D', '', stock))
                except ValueError:
                    continue

                key = f"{supplier}:{sku}"
                prev_qty = previous_data.get(key, stock_qty)

                threshold = alert_thresholds.get(sku, 10)
                if stock_qty < threshold and prev_qty >= threshold:
                    print(f"ALERT: {supplier} - {sku} dropped to {stock_qty}")

                previous_data[key] = stock_qty

        time.sleep(check_interval)

Typische Fehler und ihre Behebung

Problem Ursache Lösung
Layout der Lieferantenseite hat sich geändert Neugestaltung der Website CSS-Selektoren aktualisieren
CAPTCHA bei jeder Abfrage Zu häufiges Prüfen Intervall zwischen den Prüfungen erhöhen
Sitzung läuft mitten in der Prüfung ab Portal-Timeout Sticky Session verwenden und zügiger prüfen
Ratendaten fehlen Anmeldung erforderlich Authentifizierungsschritt ergänzen
Falsche Preise angezeigt Geobasierte Preisgestaltung Proxy-Region passend zum Zielmarkt wählen

DSGVO und Betrieb in der DACH-Region

Bestands-, Preis- und Fahrplandaten sind in der Regel keine personenbezogenen Daten. Sobald jedoch IP-Adressen oder Kontaktdaten von Ansprechpartnern in den Fluss geraten, gelten diese nach DSGVO als personenbezogen – prüfen Sie dann Rechtsgrundlage und Datenfluss, bevor Sie skalieren. Halten Sie sich zudem an die Nutzungsbedingungen der Portale. Diese Sorgfalt liegt bei Ihnen als Betreiber, nicht bei CaptchaAI.


Häufige Fragen

Was kostet die Überwachung vieler Lieferantenportale?

CaptchaAI rechnet pro gleichzeitigem Thread ab, nicht pro Lösung – innerhalb eines Plans lösen Sie unbegrenzt viele CAPTCHAs. BASIC startet bei 15 $/Monat mit 5 Threads, ADVANCE bietet 90 $/Monat mit 50 Threads. Aktuelle Tarife: captchaai.com/pricing.

Wie geht CaptchaAI mit reCAPTCHA v3 auf Warenbörsen um?

reCAPTCHA v3 arbeitet ohne Klick-Rätsel und bewertet die Anfrage über einen Score. CaptchaAI liefert dafür ein Token samt Zielaktion, das Sie wie bei v2 übermitteln – so bleiben auch Spotpreis-Feeds erreichbar.

Ist das Scraping von Lieferantenportalen rechtlich unbedenklich?

Das hängt vom Portal ab. Datenzugriff im Rahmen einer Geschäftsbeziehung ist meist unkritisch; achten Sie dennoch auf Nutzungsbedingungen, Rechtsgrundlage und DSGVO, sobald personenbezogene Daten berührt werden.

Warum sehe ich je nach Region unterschiedliche Preise?

Viele Portale zeigen Preise und Raten geobasiert anhand der IP-Adresse. Wählen Sie eine Proxy-Region im Zielmarkt, damit die erfassten Werte zu Ihrem Einkaufsgebiet passen.


Verwandte Leitfäden


Halten Sie Ihre Lieferkette sichtbar – holen Sie sich Ihren CaptchaAI-Schlüssel und automatisieren Sie die Datenerfassung über alle Lieferantenportale hinweg.

Kommentare sind für diesen Artikel deaktiviert.