Frachtraten ändern sich mit Kraftstoffzuschlag, Auslastung und Route fast täglich – wer im Einkauf oder in der Tarifüberwachung mithalten will, muss Spediteur-Preise automatisiert abgreifen. Der Haken: Tarifrechner und Trackingportale von UPS, DHL oder Frachtmaklern sind mit reCAPTCHA v2, Cloudflare Turnstile und Bild-CAPTCHAs abgesichert. CaptchaAI löst genau diese Abfragen per API und gibt das Token zurück, das Ihr Scraper in das Formular einträgt – der Rest bleibt reines Requests-und-Parsing-Handwerk.
Dieser Leitfaden zeigt, welche CAPTCHA-Typen auf welchen Frachtportalen auftauchen, wie ein Multi-Carrier-Scraper und ein Massen-Tracker aufgebaut sind und worauf Sie bei Intervallen, Fehlern und Recht achten sollten.
Welche CAPTCHAs auf Frachtportalen warten
Nicht jeder Spediteur setzt denselben Schutz ein. Paketdienste und Zollportale arbeiten meist mit reCAPTCHA v2, Frachtmakler zunehmend mit Cloudflare Turnstile, und Reedereien schützen ihre Container-Tracker häufig nur mit einfachen Bild-CAPTCHAs. Die folgende Übersicht ordnet die gängigsten Fälle ein:
| Spediteur-Typ | CAPTCHA | Geschützte Seiten | Anwendungsfall |
|---|---|---|---|
| Paketdienste (UPS, DHL, FedEx) | reCAPTCHA v2 | Tarifrechner, Sendungsverfolgung | Tarifvergleich |
| Frachtmakler | Cloudflare Turnstile | Angebotsanfragen | Frachtraten-Vergleich |
| Reedereien | Bild-CAPTCHA | Schiffs- und Containerverfolgung | Containertracking |
| Zollportale | reCAPTCHA v2 | Zollrechner | Zollabschätzung |
| Letzte-Meile-Zusteller | reCAPTCHA v2 | Tracking-Seiten | Lieferüberwachung |
| Postdienstleister | Bild-CAPTCHA | Tarifrechner | Internationale Tarife |
Alle drei Typen – reCAPTCHA v2, Turnstile und Bild-/OCR-CAPTCHA – gehören zum regulär unterstützten Umfang von CaptchaAI, sodass ein einziger Scraper alle Portalarten abdecken kann.
Tarife über mehrere Spediteure hinweg scrapen
Der Kern eines Tarif-Scrapers ist immer gleich: Seite abrufen, prüfen, ob ein data-sitekey vorhanden ist, das passende CAPTCHA lösen und das Token unter dem richtigen Feldnamen mit den Sendungsdaten absenden. Für reCAPTCHA v2 ist das g-recaptcha-response, für Turnstile cf-turnstile-response. Die folgende Klasse fragt mehrere Spediteure nacheinander ab und sortiert die Angebote nach Preis:
import requests
import time
import re
import base64
from bs4 import BeautifulSoup
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")
class ShippingRateScraper:
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_rate(self, carrier, shipment):
"""Get shipping rate from a single carrier."""
url = carrier["rate_url"]
resp = self.session.get(url, timeout=30)
# Handle CAPTCHA
sitekey = self._extract_sitekey(resp.text)
if sitekey:
if carrier.get("captcha_type") == "turnstile":
token = solve_turnstile(sitekey, url)
field = "cf-turnstile-response"
else:
token = solve_recaptcha(sitekey, url)
field = "g-recaptcha-response"
resp = self.session.post(url, data={
**shipment,
field: token,
})
else:
resp = self.session.post(url, data=shipment)
if resp.status_code == 200:
return self._parse_rates(resp.text)
return None
def compare_rates(self, carriers, shipment):
"""Compare rates across multiple carriers."""
results = []
for carrier in carriers:
try:
rates = self.get_rate(carrier, shipment)
results.append({
"carrier": carrier["name"],
"status": "success",
"rates": rates,
})
except Exception as e:
results.append({
"carrier": carrier["name"],
"status": "error",
"error": str(e),
})
time.sleep(3)
# Sort by cheapest
successful = [r for r in results if r["rates"]]
if successful:
successful.sort(
key=lambda x: float(
re.sub(r'[^\d.]', '', x["rates"][0].get("price", "9999"))
)
)
return {
"shipment": shipment,
"results": results,
"cheapest": successful[0] if successful else None,
}
def _extract_sitekey(self, html):
match = re.search(r'data-sitekey="([^"]+)"', html)
return match.group(1) if match else None
def _parse_rates(self, html):
soup = BeautifulSoup(html, "html.parser")
rates = []
for row in soup.select(".rate-option, .shipping-option, tr.rate"):
service = row.select_one(".service-name, td:first-child")
price = row.select_one(".price, .rate-amount, td:nth-child(2)")
eta = row.select_one(".delivery-time, .eta, td:nth-child(3)")
if service:
rates.append({
"service": service.get_text(strip=True),
"price": price.get_text(strip=True) if price else "",
"eta": eta.get_text(strip=True) if eta else "",
})
return rates
# Usage
scraper = ShippingRateScraper(
proxy="http://user:pass@residential.proxy.com:5000"
)
carriers = [
{"name": "Carrier A", "rate_url": "https://carrier-a.example.com/rates", "captcha_type": "recaptcha"},
{"name": "Carrier B", "rate_url": "https://carrier-b.example.com/rates", "captcha_type": "turnstile"},
{"name": "Carrier C", "rate_url": "https://carrier-c.example.com/rates", "captcha_type": "recaptcha"},
]
shipment = {
"origin_zip": "10001",
"dest_zip": "90210",
"weight": "10",
"length": "12",
"width": "8",
"height": "6",
}
comparison = scraper.compare_rates(carriers, shipment)
print(f"Cheapest: {comparison['cheapest']}")
Der Aufbau ist bewusst spediteuragnostisch: Jeder Eintrag in carriers trägt seine rate_url und seinen captcha_type, sodass Sie neue Portale nur in die Liste aufnehmen müssen. Über einen Residential-Proxy verteilen Sie die Anfragen auf verschiedene IP-Adressen und senken so das Sperrrisiko.
Sendungen in großen Mengen verfolgen
Neben dem Tarifvergleich ist die Sendungsverfolgung der zweite große Anwendungsfall. Der folgende Tracker verarbeitet eine Liste von Sendungsnummern, löst bei Bedarf das reCAPTCHA v2 der Tracking-Seite und extrahiert Status, Standort und Ereignisverlauf pro Sendung:
class ShipmentTracker:
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 track(self, carrier_url, tracking_number, sitekey=None):
"""Track a single shipment."""
resp = self.session.get(carrier_url, timeout=30)
data = {"tracking_number": tracking_number}
if sitekey:
token = solve_recaptcha(sitekey, carrier_url)
data["g-recaptcha-response"] = token
resp = self.session.post(carrier_url, data=data)
if resp.status_code == 200:
soup = BeautifulSoup(resp.text, "html.parser")
return {
"tracking": tracking_number,
"status": self._text(soup, ".status, .tracking-status"),
"location": self._text(soup, ".location, .current-location"),
"eta": self._text(soup, ".eta, .estimated-delivery"),
"events": self._parse_events(soup),
}
return None
def bulk_track(self, carrier_url, tracking_numbers, sitekey=None):
"""Track multiple shipments."""
results = []
for tn in tracking_numbers:
try:
info = self.track(carrier_url, tn, sitekey)
results.append(info or {"tracking": tn, "status": "not found"})
except Exception as e:
results.append({"tracking": tn, "error": str(e)})
time.sleep(3)
return results
def _text(self, soup, selector):
el = soup.select_one(selector)
return el.get_text(strip=True) if el else ""
def _parse_events(self, soup):
events = []
for event in soup.select(".tracking-event, .event-row"):
events.append({
"date": self._text(event, ".date, .event-date"),
"description": self._text(event, ".desc, .event-desc"),
"location": self._text(event, ".loc, .event-location"),
})
return events
Die kurze Pause von drei Sekunden zwischen den Abfragen (time.sleep(3)) ist kein Zierrat: Sie hält die Anfragefrequenz niedrig genug, dass Tracking-Portale nicht sofort eine CAPTCHA-Welle oder eine IP-Sperre auslösen.
Überwachungsintervalle sinnvoll planen
Nicht jede Datenklasse muss gleich oft aktualisiert werden. Inländische Pakettarife ändern sich langsamer als aktive Sendungen, Seefracht-Relationen langsamer als Stückgut. Ein gestaffelter Plan hält das Anfragevolumen – und damit die Zahl ausgelöster CAPTCHAs – niedrig:
| Datentyp | Intervall | Volumen |
|---|---|---|
| Inländische Pakettarife | wöchentlich | 50–100 Relationen |
| Internationale Tarife | monatlich | 20–50 Länderpaare |
| LTL-Stückgut | täglich | 10–30 Relationen |
| Seefracht | wöchentlich | 10–20 Hafenpaare |
| Sendungsverfolgung | alle 4 Stunden | pro aktiver Sendung |
Typische Fehler beim Tarif-Scraping beheben
Die meisten Probleme im Betrieb haben eine überschaubare Zahl von Ursachen. Diese Tabelle bündelt die häufigsten:
| Problem | Ursache | Lösung |
|---|---|---|
| Tarifrechner liefert kein Ergebnis | CAPTCHA vor dem Absenden nicht gelöst | Erst das CAPTCHA lösen, dann das Formular absenden |
| Tracking meldet „nicht gefunden" | Falscher Zusteller oder verzögerter Scan | Format der Sendungsnummer prüfen |
| Abweichender Preis gegenüber der Website | Fehlende Zuschläge/Gebühren | Auf Kraftstoffzuschlag-Felder prüfen |
| Zeitüberschreitung im Tarifformular | Mehrstufiger Ablauf hat zu lange gedauert | Sticky-Session-Proxy nutzen und schneller lösen |
| IP vom Spediteur gesperrt | Zu viele Tarifanfragen | Auf 20–30 Angebote/Stunde drosseln (Rate-Limiting) |
Wenn Sperren gehäuft auftreten, hilft ein Wechsel der Proxy-Strategie meist mehr als jede Feinjustierung am Parser – etwa der Umstieg von rotierenden auf Sticky-Sessions.
DSGVO und rechtliche Einordnung im DACH-Raum
Tracking-Daten sind selten anonym: Empfängernamen, Adressen und teils IP-Adressen zählen nach DSGVO als personenbezogene Daten. Wer solche Daten in großem Umfang erhebt, sollte vorab die Rechtsgrundlage klären, nur eigene oder autorisierte Sendungen abfragen und die Nutzungsbedingungen sowie die robots.txt der Zielportale respektieren. Das ist kein Compliance-Versprechen seitens CaptchaAI, sondern reader-seitige Sorgfalt – gerade im DACH-Raum, wo die vergleichende Werbung nach UWG § 6 nachprüfbare, sachliche Angaben verlangt, wenn Sie Tarife öffentlich gegenüberstellen.
Welcher CaptchaAI-Tarif passt zum Logistik-Scraping
CaptchaAI rechnet nicht pro gelöstem CAPTCHA ab, sondern pro Thread – also pro gleichzeitig laufender Abfrage. Jeder Thread löst im Abrechnungsmonat unbegrenzt viele CAPTCHAs; es gibt keine Tageslimits und keine Aufpreise nach CAPTCHA-Typ. Für die meisten Tarif-Scraper mit 20–30 Angeboten pro Stunde reicht bereits BASIC (15 $/Monat, 5 Threads). Wer mehrere Spediteure parallel abfragt oder die Sendungsverfolgung im 4-Stunden-Takt fährt, arbeitet mit STANDARD (30 $/Monat, 15 Threads) komfortabler. Die Preise sind in US-Dollar ausgewiesen; skaliert Ihr Bedarf weiter, führen ADVANCE und PREMIUM den Ausbau bis zu 100 Threads fort.
Häufige Fragen
Ist das Scrapen von Versandtarifen in Deutschland rechtlich zulässig?
Öffentlich zugängliche Tarifdaten dürfen grundsätzlich erhoben werden, doch die Nutzungsbedingungen und die robots.txt des Portals sind maßgeblich. Sobald personenbezogene Tracking-Daten ins Spiel kommen, greift die DSGVO – klären Sie die Rechtsgrundlage und beschränken Sie sich auf eigene oder autorisierte Sendungen.
Welcher CaptchaAI-Tarif reicht für Logistik-Scraping?
Für die meisten Anwendungsfälle genügt BASIC (15 $/Monat, 5 Threads). Da pro Thread unbegrenzt viele CAPTCHAs gelöst werden, skaliert erst der Bedarf an paralleler Abfrage – nicht die Zahl der Lösungen – zu STANDARD (30 $/Monat, 15 Threads) hoch.
Wie vermeide ich IP-Sperren beim Tarif-Scraping?
Drosseln Sie die Frequenz auf 20–30 Angebote pro Stunde, verteilen Sie Anfragen über Residential-Proxys und halten Sie eine Session pro Spediteur stabil. Bei authentifizierten Proxys gehört das korrekt gesetzte Zugangsverfahren zwingend dazu.
Löst CaptchaAI auch die Bild-CAPTCHAs von Reedereien?
Ja. Die einfachen Bild- und OCR-CAPTCHAs auf Container-Tracking-Seiten gehören zum unterstützten Umfang. Sie senden das Bild an die API und erhalten den erkannten Text zurück, den Ihr Tracker in das Formular einträgt.
Verwandte Leitfäden
- Lieferketten überwachen trotz CAPTCHA
- Sticky- vs. rotierende Sessions
- Proxy-Authentifizierung für Scraper
Vergleichen Sie Spediteur-Tarife automatisiert – holen Sie sich Ihren CaptchaAI-Schlüssel und lösen Sie CAPTCHAs auf Frachtportalen zuverlässig.