Ein Preis-Scraper, der gestern sauber lief, liefert heute Datenlücken – nicht weil sich die Shops geändert haben, sondern weil eine reCAPTCHA- oder Turnstile-Abfrage die Anfrage stillschweigend abgefangen hat. CaptchaAI löst diese Abfragen automatisch, sodass Ihr Monitoring ohne manuelle Eingriffe weiterläuft. Dieser Leitfaden zeigt den Aufbau: Architektur, Python-Code und Thread-basierte Kosten.
Warum Preis-Scraper an CAPTCHAs scheitern
Sobald ein Bot in kurzer Zeit viele Produktseiten abruft, blenden die großen Plattformen eine CAPTCHA-Abfrage ein:
| Plattform | CAPTCHA-Typ | Auslöser |
|---|---|---|
| Amazon | Bild-CAPTCHA, reCAPTCHA | Hohes Anfragevolumen |
| Walmart | Cloudflare Turnstile | Bot-Erkennung |
| eBay | reCAPTCHA v2 | Verdächtige Muster |
| Best Buy | Cloudflare Challenge | Gesamter automatisierter Traffic |
| Shopify-Shops | reCAPTCHA v3 | Variiert je nach Shop-Konfiguration |
Ohne CAPTCHA-Behandlung bricht die Pipeline nicht mit einem Fehler ab – sie liefert die CAPTCHA-Seite statt der Produktseite. Das Ergebnis sind stille Datenlücken, die oft erst auffallen, wenn eine Preisänderung im Report fehlt.
Dabei unterscheiden sich die Abfragen im Verhalten: reCAPTCHA v2 blendet die bekannte Checkbox oder eine Bildaufgabe ein, reCAPTCHA v3 arbeitet unsichtbar mit einem Score im Hintergrund, und Cloudflare Turnstile schaltet je nach Risikobewertung eine kurze Prüfung dazwischen. CaptchaAI liefert für alle drei ein gültiges Token zurück, das Sie in dieselbe Anfrage einsetzen.
Im DACH-Markt gilt das genauso: Wer Konkurrenzpreise gegen idealo oder Geizhals.at abgleicht oder eigene Shopware- und JTL-Shops beobachtet, trifft auf dieselben reCAPTCHA-v3- und Turnstile-Schranken. Und: Preis-Scraping berührt die AGB der Zielshops sowie – bei personenbezogenen Daten – die DSGVO. Prüfen Sie Ihre Rechtsgrundlage vor der Erhebung im großen Stil.
Architektur
Ein tragfähiges Monitoring trennt Zeitplanung, Abruf und CAPTCHA-Lösung voneinander:
Scheduler (every 30 min)
→ URL Queue
→ Scraper Workers (5-10 concurrent)
→ Fetch page
→ CAPTCHA detected?
→ Yes → CaptchaAI → Solve → Retry page
→ No → Parse prices
→ Store in database
→ Alert on price changes
Nur wenn eine Abfrage erkannt wird, geht die Anfrage an CaptchaAI – alle übrigen Seiten laufen direkt in die Preis-Extraktion. Jeder ermittelte Preis landet mit Zeitstempel in der Datenbank; ein Vergleich mit dem letzten Wert löst nur dann eine Benachrichtigung aus, wenn die Abweichung eine Schwelle von etwa 5 % überschreitet. So bleiben kleine Schwankungen im Rauschen und echte Preisänderungen fallen sofort auf.
Umsetzung
Preismonitor (Python)
Kern ist eine Hilfsfunktion: Sitekey aus dem HTML ziehen, Solve-Auftrag an in.php übermitteln, Ergebnis über res.php abfragen und das gelöste Token in das Formular eintragen.
import requests
import time
import re
import json
import os
from datetime import datetime
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
BASE_URL = "https://ocr.captchaai.com"
def solve_captcha(method, params):
params["key"] = API_KEY
params["method"] = method
resp = requests.get(f"{BASE_URL}/in.php", params=params)
if not resp.text.startswith("OK|"):
raise Exception(f"Submit failed: {resp.text}")
task_id = resp.text.split("|")[1]
for _ in range(60):
time.sleep(5)
result = requests.get(f"{BASE_URL}/res.php", params={
"key": API_KEY, "action": "get", "id": task_id,
})
if result.text == "CAPCHA_NOT_READY":
continue
if result.text.startswith("OK|"):
return result.text.split("|", 1)[1]
raise Exception(f"Solve failed: {result.text}")
raise TimeoutError("CAPTCHA solve timed out")
def fetch_with_captcha(url, session):
"""Fetch a page, solving CAPTCHAs if encountered."""
resp = session.get(url)
# Check for reCAPTCHA
match = re.search(r'data-sitekey=["\']([A-Za-z0-9_-]+)["\']', resp.text)
if match:
site_key = match.group(1)
token = solve_captcha("userrecaptcha", {
"googlekey": site_key,
"pageurl": url,
})
resp = session.post(url, data={"g-recaptcha-response": token})
# Check for Turnstile
match = re.search(
r'class="cf-turnstile"[^>]*data-sitekey=["\']([^"\']+)', resp.text
)
if match:
site_key = match.group(1)
token = solve_captcha("turnstile", {
"sitekey": site_key,
"pageurl": url,
})
resp = session.post(url, data={"cf-turnstile-response": token})
return resp
def extract_price(html, selectors):
"""Extract price from HTML using regex patterns."""
for pattern in selectors:
match = re.search(pattern, html)
if match:
price_str = match.group(1).replace(",", "")
return float(price_str)
return None
def monitor_prices(products):
"""Monitor prices for a list of products."""
session = requests.Session()
session.headers["User-Agent"] = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/120.0.0.0"
)
results = []
for product in products:
try:
resp = fetch_with_captcha(product["url"], session)
price = extract_price(resp.text, product["selectors"])
results.append({
"name": product["name"],
"url": product["url"],
"price": price,
"timestamp": datetime.utcnow().isoformat(),
"status": "ok",
})
print(f" {product['name']}: ${price}")
except Exception as e:
results.append({
"name": product["name"],
"url": product["url"],
"price": None,
"timestamp": datetime.utcnow().isoformat(),
"status": f"error: {e}",
})
print(f" {product['name']}: ERROR - {e}")
return results
# Define products to monitor
products = [
{
"name": "Wireless Headphones",
"url": "https://example.com/product/headphones",
"selectors": [
r'class="price"[^>]*>\$?([\d,]+\.?\d*)',
r'itemprop="price" content="([\d.]+)"',
],
},
{
"name": "Bluetooth Speaker",
"url": "https://example.com/product/speaker",
"selectors": [
r'class="price"[^>]*>\$?([\d,]+\.?\d*)',
],
},
]
print("Starting price check...")
results = monitor_prices(products)
# Save results
with open("prices.json", "w") as f:
json.dump(results, f, indent=2)
Node.js-Implementierung
Dieselbe Logik in Node.js mit axios und cheerio – praktisch, wenn Ihr Stack ohnehin auf JavaScript aufsetzt:
const axios = require("axios");
const cheerio = require("cheerio");
const API_KEY = process.env.CAPTCHAAI_API_KEY;
async function solveCaptcha(method, params) {
params.key = API_KEY;
params.method = method;
const submit = await axios.get("https://ocr.captchaai.com/in.php", {
params,
});
const taskId = String(submit.data).split("|")[1];
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const poll = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: taskId },
});
const text = String(poll.data);
if (text === "CAPCHA_NOT_READY") continue;
if (text.startsWith("OK|")) return text.split("|").slice(1).join("|");
throw new Error(text);
}
throw new Error("Timeout");
}
async function monitorPrice(url) {
const resp = await axios.get(url);
const $ = cheerio.load(resp.data);
// Check for reCAPTCHA
const siteKey = $(".g-recaptcha").attr("data-sitekey");
if (siteKey) {
const token = await solveCaptcha("userrecaptcha", {
googlekey: siteKey,
pageurl: url,
});
// Re-fetch with token
const formResp = await axios.post(url, { "g-recaptcha-response": token });
return cheerio.load(formResp.data);
}
const price = $('[itemprop="price"]').attr("content") || $(".price").text();
return parseFloat(price.replace(/[^0-9.]/g, ""));
}
Kosten und Plan-Auswahl
Ein häufiges Missverständnis: CaptchaAI rechnet nicht pro gelöstem CAPTCHA ab, sondern pro gleichzeitigem Thread – jeder Plan enthält unbegrenzte Lösungen pro Thread. Für ein Preismonitoring zählt also, wie viele Scraper-Worker parallel laufen, nicht die Gesamtzahl der Seiten.
| Monitoring-Umfang | Gleichzeitige Worker | Passender Plan |
|---|---|---|
| Einzelne Shops, stündlich | bis 5 Threads | BASIC (15 $/Monat, 5 Threads) |
| Mittleres Portfolio, alle 15 Min | bis 15 Threads | STANDARD (30 $/Monat, 15 Threads) |
| Große Kataloge, hohe Frequenz | bis 50 Threads | ADVANCE (90 $/Monat, 50 Threads) |
Alle Angaben in US-Dollar. Nicht jeder Seitenaufruf löst eine Abfrage aus, sodass ein Thread pro Minute mehrere Seiten bedient – ein einzelner Worker deckt damit ein überraschend großes Katalog-Segment ab. Wächst der Bedarf, skalieren Sie einfach auf den nächsthöheren Plan mit mehr Threads, ohne den Code zu ändern.
Terminplanung
Führen Sie die Prüfungen alle 30 Minuten per cron aus:
# crontab -e
*/30 * * * * cd /opt/monitor && python price_monitor.py >> /var/log/prices.log 2>&1
Alternativ übernimmt Pythons schedule-Bibliothek die Taktung direkt im Prozess:
import schedule
schedule.every(30).minutes.do(lambda: monitor_prices(products))
while True:
schedule.run_pending()
time.sleep(60)
Worker laufen etwa auf Hetzner, IONOS oder netcup; die Taktung lässt sich auch über GitLab CI oder GitHub Actions statt cron auslösen.
Blockaden zuverlässig vermeiden
Ein gelöstes CAPTCHA ist nur ein Teil der Zuverlässigkeit. Damit einzelne IP-Adressen nicht gedrosselt werden, verteilen Sie die Anfragen über die Zeit statt sie im Burst abzufeuern und rotieren Sie Proxys sowie User-Agents. Für stabile Läufe haben sich Residential-Proxys bewährt, während ein Rechenzentrums-Proxy für unkritische Ziele meist ausreicht. Wichtig ist ein sauberer Wiederholungsmechanismus: Schlägt ein Abruf fehl, wiederholen Sie ihn mit exponentiellem Backoff, statt sofort erneut anzufragen. So bleibt die Erfolgsquote hoch, ohne dass Ihre Worker ein auffälliges Anfragemuster erzeugen.
FAQ
Welche CAPTCHA-Typen treten bei Preis-Scraping am häufigsten auf?
reCAPTCHA v2 und v3 sowie Cloudflare Turnstile und Challenge – alle vier löst CaptchaAI über die API. Dazu kommen Bild- und Raster-CAPTCHAs, die manche Marktplätze bei Verdacht einblenden.
Wie viel Verzögerung bringt das CAPTCHA-Lösen pro Seite?
Nur Seiten mit aktiver Abfrage durchlaufen den Solve-Schritt; alle anderen bleiben schnell. Für die betroffenen Seiten kommen einige Sekunden Lösungszeit hinzu – deshalb lohnt sich Parallelität über mehrere Worker.
Rechnet CaptchaAI pro gelöstem CAPTCHA oder pro Thread ab?
Pro Thread. Jeder Plan – ab BASIC (15 $/Monat, 5 Threads) – enthält unbegrenzte Lösungen pro Thread, ohne Tageslimits oder Aufpreise nach CAPTCHA-Typ. Ihre Kosten hängen an der Parallelität, nicht am Volumen.
Ist Preis-Scraping in Deutschland rechtlich zulässig?
Das hängt vom Einzelfall ab. Öffentlich sichtbare Preisdaten sind meist weniger heikel, doch die AGB der Zielshops und – bei personenbezogenen Daten – die DSGVO setzen Grenzen. Verbindliche Auskunft gibt nur eine Rechtsberatung.