Suchergebnisse trotz Captcha scrapen (Python-Leitfaden, 2026)
Suchergebnisseiten gehören zu den ersten Stellen, an denen ein Scraper auf ein Captcha trifft. Die Seite sieht einen Schwall von Anfragen von einer Adresse, zeigt eine Herausforderung und liefert keine Ergebnisse, bis die Herausforderung bestanden ist. Um Suchergebnisse trotz Captcha zu scrapen, braucht dein Skript drei Schritte: die Herausforderung erkennen, bevor du parst, sie mit einem API-Aufruf lösen und das Token absenden, damit die Seite die echte Ergebnisseite zurückgibt.
Dieser Leitfaden zeigt diesen Ablauf in Python mit requests und BeautifulSoup. Er behandelt Seiten, die ein PopularCaptcha-Widget zeigen, also den Typ, den Scarnox heute löst (proxyless). Starte mit kostenlosem Testguthaben, ohne Karte und führe den Code zuerst auf einer Seite aus, die dir gehört.
Wie die Herausforderung aussieht
Suchseiten setzen das Widget meist in ein Formular. Der Widget-Container trägt ein Attribut data-sitekey, und das Formular sendet zurück an die Seite:
<form action="/search/verify" method="post">
<input type="hidden" name="q" value="web scraping">
<input type="hidden" name="lang" value="en">
<div class="h-captcha" data-sitekey="YOUR-SITEKEY-UUID"></div>
<button type="submit">Continue</button>
</form>
Dein Code braucht drei Dinge aus diesem Markup: den Sitekey, die Action-URL des Formulars und die versteckten Felder, die zusammen mit dem Token zurückgeschickt werden müssen. Die Hilfsfunktion unten liest alle drei aus, sodass du sie nicht fest codieren musst.
Wenn das Widget auf deinem Ziel ein anderer Captcha-Typ ist, siehe Captcha-Typen. Die Unterstützung für reCAPTCHA und Cloudflare Turnstile kommt demnächst, deshalb deckt der Code hier nur PopularCaptcha ab.
Der Solver-Aufruf
Die Lösungsanfrage geht an die Scarnox-API mit deinem Schlüssel im Bearer-Header. Der Aufruf blockiert, bis das Token bereit ist, es gibt also keine Polling-Schleife:
import time
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup
API_KEY = "scarnox_YOURAPIKEY"
SEARCH_URL = "https://search.example.com/search"
session = requests.Session()
session.headers["User-Agent"] = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36"
)
def solve(sitekey: str, page_url: str) -> str:
r = requests.post(
"https://api.scarnox.com/api/tasks/create",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"captcha_type": "PopularCaptchaTaskProxyless",
"site_key": sitekey,
"site_url": page_url,
},
timeout=185, # der Aufruf kehrt zurück, sobald das Token bereit ist
)
data = r.json()
if data.get("errorId") != 0:
raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
return data["solution"]["token"]
Eine fehlgeschlagene Lösung liefert ein errorId ungleich 0, und dafür wirst du nicht berechnet. Wiederholungen sind im Leitfaden Fehlerbehandlung bei Captcha-Lösungen beschrieben.
Die Seite abrufen und das Captcha passieren
Die Fetch-Funktion prüft zuerst auf das Widget. Gibt es keines, liefert sie das HTML unverändert zurück. Gibt es eines, löst sie die Herausforderung, baut das Formular mit denselben versteckten Feldern neu auf und sendet es ab:
def fetch_results(query: str, page: int = 1) -> str:
resp = session.get(SEARCH_URL, params={"q": query, "page": page}, timeout=30)
soup = BeautifulSoup(resp.text, "html.parser")
widget = soup.select_one("[data-sitekey]")
if widget is None:
return resp.text # keine Herausforderung auf dieser Seite
form = widget.find_parent("form")
if form is None:
raise RuntimeError("Captcha widget is not inside a form")
token = solve(widget["data-sitekey"], resp.url)
payload = {
field["name"]: field.get("value", "")
for field in form.select("input[name]")
}
payload["h-captcha-response"] = token
payload["g-recaptcha-response"] = token
action = urljoin(resp.url, form.get("action") or resp.url)
verified = session.post(action, data=payload, timeout=30)
if "data-sitekey" in verified.text:
raise RuntimeError("Challenge still shown after submit; token may be rejected")
return verified.text
Zwei Details sind hier wichtig. Die site_url ist die URL, unter der das Captcha tatsächlich ausgeliefert wird (resp.url, die auch Weiterleitungen enthält), nicht der nackte Suchendpunkt. Und die Sitzung behält Cookies zwischen GET und POST, was viele Seiten brauchen, um ein Token zu akzeptieren.
Tokens laufen in etwa zwei Minuten ab, deshalb geschieht die Lösung direkt nach dem Lesen des Formulars und nicht vorher. Mehr zu diesem Zeitfenster findest du unter Ablauf von Captcha-Tokens.
Die Ergebnisse parsen
Sobald die echte Ergebnisseite zurückkommt, ist das Parsen gewöhnliche HTML-Arbeit. Die Selektoren unten sind Platzhalter. Öffne die Seite in den DevTools und ersetze sie durch die echten:
def parse_results(html: str) -> list[dict]:
soup = BeautifulSoup(html, "html.parser")
rows = []
for item in soup.select("div.result"):
link = item.select_one("a[href]")
heading = item.select_one("h3")
if link and heading:
rows.append({
"title": heading.get_text(strip=True),
"url": link["href"],
})
return rows
if __name__ == "__main__":
for page in range(1, 4):
html = fetch_results("web scraping", page)
for row in parse_results(html):
print(row)
time.sleep(5) # Anfragen zwischen den Seiten takten
Halte die Ausgabe jeder Seite getrennt. Liefert eine Seite null Zeilen, prüfe auf das Widget, bevor du annimmst, dass die Suchanfrage keine Treffer hatte.
Verantwortungsvoll scrapen
Ein Captcha ist ein Signal, kein Schlupfloch. Bevor du dieses Skript gegen eine Suchseite laufen lässt:
- Lies robots.txt und die Nutzungsbedingungen. Viele Suchseiten verbieten automatisierte Abfragen. Wenn die Bedingungen dies untersagen, führe das Skript nicht aus.
- Begrenze die Rate. Das
time.sleep(5)oben ist eine Untergrenze, kein Ziel. Werde langsamer, wenn du mehr Herausforderungen siehst. - Begrenze, was du speicherst. Suchergebnisse können Namen, E-Mail-Adressen und andere personenbezogene Daten enthalten. Speichere nur die Felder, die du brauchst, und lösche den Rest.
- Bevorzuge eine offizielle API, wenn die Seite eine anbietet. Sie ist zuverlässiger und meist günstiger als das Scrapen von HTML.
Zur rechtlichen Seite siehe Ist Web-Scraping legal?. Um Blockaden von vornherein zu vermeiden, siehe Scrapen ohne blockiert zu werden.
Preise
Scarnox rechnet pro erfolgreicher Lösung ab, und fehlgeschlagene Lösungen sind kostenlos. Die Raten findest du auf der Preisseite. Starte mit kostenlosem Testguthaben, ohne Karte.