Anwendungsfälle

Überwachung des Einzelhandelsbestands mit CAPTCHA-Verwaltung

Sie wollen wissen, ob ein Produkt verfügbar ist, wie sich sein Preis entwickelt und wann die Konkurrenz nachzieht – doch die Produktseite blockt automatisierte Zugriffe mit einem reCAPTCHA oder einer Cloudflare-Turnstile-Abfrage. Dieser Leitfaden zeigt, wie Sie Preise, Lagerbestand und Verfügbarkeit im Online-Handel dauerhaft überwachen, während CaptchaAI die CAPTCHA-Abfragen im Hintergrund löst. Große Händler wie Amazon, Otto oder MediaMarkt spielen CAPTCHAs gezielt aus, sobald ein Zugriffsmuster nach Bot aussieht – die folgenden Python-Bausteine halten Ihre Monitoring-Pipeline trotzdem am Laufen.


CAPTCHAs im Online-Handel: Wo sie auftauchen

Bevor Sie eine Zeile Code schreiben, lohnt der Blick darauf, welche Schutzmechanismen auf welchen Plattformen greifen. Die folgende Übersicht stammt aus dem US-Handel, das Muster gilt aber genauso für DACH-Händler wie Otto, Zalando, MediaMarkt oder Preisvergleiche wie Idealo: Produktsuche, Warenkorb-Prüfung und hochfrequente Zugriffe sind die typischen Auslöser.

Plattform CAPTCHA-Typ Auslöser Daten
Amazon reCAPTCHA v2 Zugriffe mit hohem Volumen Preis, Lagerbestand, Bewertungen
Walmart reCAPTCHA v3 + Cloudflare Bot-Erkennung Bestand, Preise
Best Buy reCAPTCHA v2 Add-to-Cart-Prüfung Lagerbestand, Preise
Target Cloudflare Turnstile Automatisierter Zugriff Verfügbarkeit
Shopify-Shops Cloudflare Turnstile Rate-Limiting Produktdaten
eBay reCAPTCHA v2 Suche + Listing-Zugriff Angebote, Preise

CaptchaAI löst die hier relevanten Typen – reCAPTCHA v2, reCAPTCHA v3 und Cloudflare Turnstile – über eine einzige API. hCaptcha und FunCaptcha werden nicht unterstützt; falls eine Zielseite darauf umstellt, greifen die Beispiele unten nicht.


Prüfintervall und Proxy-Wahl für die Bestandsüberwachung

Die wichtigste Entscheidung fällt vor dem ersten Request: Wie oft prüfen Sie, und über welche Proxys? Zu aggressive Intervalle provozieren mehr CAPTCHAs und IP-Sperren, zu träge Intervalle verpassen kurzfristige Preis- oder Verfügbarkeitsänderungen. Als Ausgangspunkt hat sich diese Staffelung nach Produkttyp bewährt:

Produkttyp Prüfintervall Proxy-Typ
Elektronik alle 30 Min rotierender Residential-Proxy
Lebensmittel alle 4 Stunden rotierender Residential-Proxy
Mode alle 2 Stunden Residential-Proxy
Limitierte Drops jede Minute Mobiler Proxy
Haushaltswaren alle 6 Stunden Residential-Proxy
Standardware alle 12 Stunden Rechenzentrums-Proxy (oft ausreichend)

Residential- und Mobile-Proxys sind teurer, lösen aber deutlich seltener eine CAPTCHA-Abfrage aus als Rechenzentrums-IPs. Für Standardware mit geringem Schutz reichen letztere; bei limitierten Releases mit Minutentakt führt an Mobile-Proxys kaum ein Weg vorbei.


Produktseiten überwachen: der RetailMonitor

Der Kern ist eine RetailMonitor-Klasse, die eine Produktseite abruft, eine eventuelle CAPTCHA-Abfrage über CaptchaAI löst, den Request wiederholt und anschließend Titel, Preis und Verfügbarkeit aus dem HTML extrahiert. Die Methode monitor_products läuft in einer Schleife und meldet nur, wenn sich Preis oder Lagerstatus gegenüber dem letzten Zyklus geändert haben.

import requests
import time
import re
import json
from datetime import datetime
from bs4 import BeautifulSoup

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


def solve_captcha(method, sitekey, pageurl, **kwargs):
    data = {
        "key": CAPTCHAAI_KEY, "method": method,
        "googlekey": sitekey, "pageurl": pageurl, "json": 1,
    }
    data.update(kwargs)
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data=data)
    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,
        })
        r = result.json()
        if r["request"] != "CAPCHA_NOT_READY":
            return r["request"]
    raise TimeoutError("Timeout")


class RetailMonitor:
    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",
            "Accept": "text/html,application/xhtml+xml,*/*;q=0.8",
            "Accept-Language": "en-US,en;q=0.9",
        })

    def check_product(self, url):
        """Check single product's price and availability."""
        resp = self.session.get(url, timeout=30)

        # Handle CAPTCHA
        if self._has_captcha(resp.text):
            resp = self._solve_and_retry(resp.text, url)

        soup = BeautifulSoup(resp.text, "html.parser")
        return {
            "url": url,
            "title": self._text(soup, "h1, .product-title, #productTitle"),
            "price": self._text(soup, ".price, .a-price .a-offscreen, .prod-price"),
            "availability": self._text(soup, "#availability, .stock-status, .fulfillment"),
            "in_stock": self._check_stock(soup),
            "timestamp": datetime.now().isoformat(),
        }

    def monitor_products(self, product_urls, interval_sec=1800):
        """Continuously monitor products for changes."""
        history = {}

        while True:
            for url in product_urls:
                try:
                    current = self.check_product(url)

                    # Check for changes
                    prev = history.get(url)
                    if prev:
                        changes = self._detect_changes(prev, current)
                        if changes:
                            self._alert(current["title"], changes)

                    history[url] = current
                    time.sleep(3)

                except Exception as e:
                    print(f"Error checking {url}: {e}")

            print(f"Cycle complete: {len(product_urls)} products checked")
            time.sleep(interval_sec)

    def track_prices(self, product_urls, output_file="prices.json"):
        """Single price check across all products."""
        results = []
        for url in product_urls:
            try:
                data = self.check_product(url)
                results.append(data)
                time.sleep(3)
            except Exception as e:
                results.append({"url": url, "error": str(e)})

        with open(output_file, "w") as f:
            json.dump(results, f, indent=2)
        print(f"Tracked {len(results)} products → {output_file}")
        return results

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

    def _solve_and_retry(self, html, url):
        match = re.search(r'data-sitekey="([^"]+)"', html)
        if not match:
            return self.session.get(url)

        sitekey = match.group(1)
        if 'cf-turnstile' in html:
            token = solve_captcha("turnstile", sitekey, url)
            return self.session.post(url, data={"cf-turnstile-response": token})
        else:
            token = solve_captcha("userrecaptcha", sitekey, url)
            return self.session.post(url, data={"g-recaptcha-response": token})

    def _text(self, soup, selector):
        el = soup.select_one(selector)
        return el.get_text(strip=True) if el else ""

    def _check_stock(self, soup):
        stock_el = soup.select_one("#availability, .stock-status")
        if stock_el:
            text = stock_el.get_text(strip=True).lower()
            return "in stock" in text or "available" in text
        return None

    def _detect_changes(self, prev, current):
        changes = []
        if prev["price"] != current["price"]:
            changes.append(f"Price: {prev['price']} → {current['price']}")
        if prev["in_stock"] != current["in_stock"]:
            status = "In Stock" if current["in_stock"] else "Out of Stock"
            changes.append(f"Stock: → {status}")
        return changes

    def _alert(self, title, changes):
        print(f"ALERT [{title}]: {', '.join(changes)}")


# Usage
monitor = RetailMonitor(
    proxy="http://user:pass@residential.proxy.com:5000"
)

products = [
    "https://store.example.com/product/abc123",
    "https://store.example.com/product/def456",
    "https://store.example.com/product/ghi789",
]

# One-time price check
results = monitor.track_prices(products)

# Or continuous monitoring (every 30 min)
# monitor.monitor_products(products, interval_sec=1800)

Der Ablauf ist bei jeder Zielseite gleich: _has_captcha erkennt Sitekey- und Turnstile-Marker im HTML, _solve_and_retry liest den data-sitekey aus, wählt anhand des Markers zwischen turnstile und userrecaptcha und reicht das gelöste Token als cf-turnstile-response oder g-recaptcha-response wieder ein. So bleibt der Rest Ihrer Logik von der CAPTCHA-Verarbeitung getrennt.


Ganze Kategorien nach Lagerbestand scannen

Für einzelne SKUs reicht check_product. Wollen Sie dagegen eine komplette Kategorie erfassen – etwa alle Notebooks eines Shops –, blättern Sie seitenweise durch die Listing-Seiten. Die Funktion nutzt dieselbe RetailMonitor-Instanz und bricht ab, sobald eine Seite keine Treffer mehr liefert.

def scan_category(base_url, category, max_pages=20):
    """Scan an entire product category for stock status."""
    monitor = RetailMonitor(
        proxy="http://user:pass@residential.proxy.com:5000"
    )

    all_products = []
    for page in range(1, max_pages + 1):
        url = f"{base_url}/{category}?page={page}"
        resp = monitor.session.get(url, timeout=30)

        if monitor._has_captcha(resp.text):
            resp = monitor._solve_and_retry(resp.text, url)

        soup = BeautifulSoup(resp.text, "html.parser")
        items = soup.select(".product-card, .s-result-item")

        if not items:
            break

        for item in items:
            all_products.append({
                "name": monitor._text(item, ".product-name, .a-text-normal"),
                "price": monitor._text(item, ".price, .a-price"),
                "stock": monitor._text(item, ".stock, .a-color-success"),
                "url": item.select_one("a")["href"] if item.select_one("a") else "",
            })

        time.sleep(3)

    return all_products

Passen Sie die CSS-Selektoren an das Markup der jeweiligen Zielseite an – die hier genutzten Klassen decken das Amazon-typische Listing-Layout ab, ein Shopware- oder JTL-Shop verwendet andere.


Preise mehrerer Händler vergleichen

Der klassische Anwendungsfall im DACH-Raum ist der Preisvergleich: dasselbe Produkt bei mehreren Händlern suchen und nach Preis sortieren – im Prinzip das, was Idealo oder Geizhals automatisiert tun. Jeder Store bekommt seinen eigenen Proxy, damit die parallelen Anfragen nicht auf eine IP zurückfallen.

def compare_product_across_stores(product_name, stores):
    """Compare prices across retailers for the same product."""
    results = []

    for store in stores:
        monitor = RetailMonitor(proxy=store.get("proxy"))
        search_url = f"{store['base_url']}/search?q={product_name}"

        try:
            resp = monitor.session.get(search_url, timeout=30)
            if monitor._has_captcha(resp.text):
                resp = monitor._solve_and_retry(resp.text, search_url)

            soup = BeautifulSoup(resp.text, "html.parser")
            first_result = soup.select_one(".product-card, .s-result-item")

            if first_result:
                results.append({
                    "store": store["name"],
                    "price": monitor._text(first_result, ".price"),
                    "in_stock": "in stock" in first_result.get_text().lower(),
                })
        except Exception as e:
            results.append({"store": store["name"], "error": str(e)})

        time.sleep(5)

    results.sort(key=lambda x: x.get("price", "zzzz"))
    return results

Typische Fehler bei der Bestandsüberwachung beheben

Die meisten Probleme im Dauerbetrieb lassen sich einer der folgenden Ursachen zuordnen:

Problem Ursache Lösung
CAPTCHA auf jeder Produktseite IP markiert oder Rate überschritten Verzögerung erhöhen, Proxys rotieren
Falscher Preis ausgelesen Preise werden per JavaScript nachgeladen stattdessen Selenium/Puppeteer verwenden
„Robot-Check"-Seite Bot-Erkennung im Amazon-Stil realistische Header + Residential-Proxy nutzen
Lagerbestand „nicht verfügbar" geografisch eingeschränkte Verfügbarkeit Proxy der Zielregion zuordnen
Fehlende Produktdaten Seitenstruktur hat sich geändert CSS-Selektoren aktualisieren

Wenn Preise per JavaScript gerendert werden, hilft kein reines HTML-Parsing – hier führt der Weg über einen Headless-Browser wie Selenium oder Puppeteer, der CaptchaAI genauso einbinden kann.


Rechtlicher Hinweis: Preisüberwachung und DSGVO

Preis- und Verfügbarkeitsdaten sind öffentlich einsehbar, das automatisierte Auslesen im großen Stil bewegt sich rechtlich aber in einem sensiblen Bereich. Prüfen Sie vorab die AGB der Zielseite und Ihre eigene Rechtsgrundlage. Sobald Sie über Proxys arbeiten, verarbeiten Sie IP-Adressen – nach DSGVO personenbezogene Daten. Dokumentieren Sie Ihre Datenflüsse, beschränken Sie die Erfassung auf das tatsächlich Benötigte und halten Sie für vergleichende Aussagen (Stichwort UWG § 6) belastbare, datierte Messwerte bereit.


Häufige Fragen

Welche CAPTCHA-Typen tauchen bei der Bestandsüberwachung auf?

Meist reCAPTCHA v2, reCAPTCHA v3 und Cloudflare Turnstile – alle drei löst CaptchaAI über dieselbe API. hCaptcha und FunCaptcha werden nicht unterstützt; stellt eine Zielseite darauf um, greifen die hier gezeigten Beispiele nicht.

Was kostet die Überwachung vieler Produkte mit CaptchaAI?

CaptchaAI rechnet pro gleichzeitigem Thread ab, nicht pro Lösung – die Zahl der Solves pro Monat ist unbegrenzt. Für kleinere Monitoring-Jobs reicht der BASIC-Tarif (15 $/Monat, 5 Threads); wer Tausende Produkte parallel prüft, fährt mit ADVANCE (90 $/Monat, 50 Threads) besser. Preise in US-Dollar.

Warum lese ich manchmal falsche oder veraltete Preise aus?

Fast immer, weil der Preis per JavaScript nachgeladen wird und im rohen HTML noch nicht steht. Prüfen Sie den tatsächlichen HTML-Quelltext; wenn der Wert fehlt, wechseln Sie auf Selenium oder Puppeteer statt reinem Requests-Parsing.

Kann ich Tausende Produkte gleichzeitig überwachen?

Ja. Verteilen Sie die Anfragen auf mehrere Proxy-IPs und versetzen Sie die Prüfungen zeitlich. Bei 1.000 Produkten mit 3-Sekunden-Abstand dauert ein vollständiger Zyklus rund 50 Minuten – mehr Threads und Proxys verkürzen das entsprechend.


Weiterführende Anleitungen


Überwachen Sie Ihren Einzelhandelsbestand in Echtzeit – Holen Sie sich Ihren CaptchaAI-Schlüssel für die automatisierte CAPTCHA-Verarbeitung.

Kommentare sind für diesen Artikel deaktiviert.