Kurz gesagt: reCAPTCHA in einer Single-Page-Anwendung lösen Sie in vier Schritten:
- auf das gerenderte Widget warten,
- den Sitekey aus dem DOM oder dem JavaScript-Bundle auslesen,
- das Token über CaptchaAI erzeugen lassen,
- das Token dort einsetzen, wo React, Vue oder Angular es erwartet.
Die eigentliche Hürde ist das Timing, nicht das Lösen selbst: Das Widget steht nicht im ausgelieferten HTML, sondern taucht erst auf, wenn eine Komponente mountet, sich die Route ändert oder der Nutzer eine Aktion auslöst. Wer die Seitenquelle wie bei einer klassischen Seite ausliest, findet schlicht nichts. Dieser Leitfaden zeigt jeden Schritt konkret mit Playwright und Puppeteer.
Warum eine SPA eine Sonderbehandlung braucht
Bei einer klassischen serverseitig gerenderten Seite steht alles Nötige schon im HTML. Eine SPA baut die Oberfläche dagegen erst im Browser zusammen – und mit ihr das reCAPTCHA-Widget. Der Unterschied im Überblick:
| Klassische Seite | SPA |
|---|---|
| reCAPTCHA-Skript liegt im initialen HTML | Skript wird nach dem Routenwechsel nachgeladen |
| Widget rendert beim Seitenaufruf | Widget rendert beim Mount der Komponente |
| Sitekey steht in der Seitenquelle | Sitekey steckt im JavaScript-Bundle |
| Formular wird per POST übermittelt | Formular geht über XHR/fetch raus |
In der Praxis begegnet Ihnen dieses Muster überall dort, wo moderne Frameworks im Einsatz sind:
- ein React-Login, das das Widget erst beim Mount der Komponente rendert;
- ein Vue-Checkout auf Shopware- oder JTL-Basis, der reCAPTCHA erst nach dem Klick auf „Bestellung prüfen“ nachlädt;
- Behörden- und Visa-Terminportale (etwa BLS-Portale), die im DACH-Raum stark nachgefragt sind und das CAPTCHA erst nach dem Login einblenden.
Ihre Automatisierung darf das Widget also nicht sofort erwarten, sondern muss gezielt darauf warten.
Das reCAPTCHA-Widget erkennen
Auf das Element warten
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://example.com/login")
# SPA may need a click or navigation to trigger reCAPTCHA
page.click("#show-login-form")
# Wait for reCAPTCHA iframe to appear
recaptcha_frame = page.wait_for_selector(
"iframe[src*='recaptcha']",
timeout=15000
)
print("reCAPTCHA detected:", recaptcha_frame.get_attribute("src"))
Den Sitekey extrahieren
In einer SPA kann der Sitekey an mehreren Stellen liegen. Prüfen Sie sie von der zuverlässigsten zur letzten Rückfalloption:
- im
src-Attribut des reCAPTCHA-iframe (?k=…); - im
data-sitekey-Attribut des gerenderten Elements; - als letzte Option im gebündelten JavaScript.
Die folgenden drei Methoden decken genau diese Fälle ab:
# Method 1: From the iframe src
iframe_src = recaptcha_frame.get_attribute("src")
# src contains: ...?k=6LcR_RsTAAAAADge...
import re
match = re.search(r'[?&]k=([^&]+)', iframe_src)
site_key = match.group(1) if match else None
# Method 2: From data-sitekey attribute
site_key = page.eval_on_selector(
"[data-sitekey]",
"el => el.getAttribute('data-sitekey')"
)
# Method 3: From JavaScript bundle (last resort)
site_key = page.evaluate("""
() => {
// Check for grecaptcha render calls
const scripts = document.querySelectorAll('script');
for (const s of scripts) {
const match = s.textContent.match(/sitekey['":\s]+(['"])(6L[^'"]+)\\1/);
if (match) return match[2];
}
return null;
}
""")
Das reCAPTCHA lösen
Sobald der Sitekey vorliegt, übergeben Sie ihn an CaptchaAI. Der Solver braucht nur zwei Angaben:
- den
googlekey– also den extrahierten Sitekey; - die
pageurl– die aktuelle Adresse der SPA-Ansicht.
Ob das Widget in einer SPA oder auf einer klassischen Seite sitzt, spielt für den Solver keine Rolle:
import requests
import time
def solve_recaptcha(site_key, page_url):
# Submit task
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": "YOUR_API_KEY",
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url,
"json": 1
})
task_id = resp.json()["request"]
# Poll for result
for _ in range(60):
time.sleep(3)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": "YOUR_API_KEY",
"action": "get",
"id": task_id,
"json": 1
})
data = result.json()
if data["status"] == 1:
return data["request"]
raise TimeoutError("Solve timed out")
Hinweis: Ein reCAPTCHA-Token ist nur rund 120 Sekunden gültig. Lösen Sie es deshalb erst kurz vor der Übermittlung und nicht auf Vorrat, sonst läuft es ab, bevor Ihr Zielaufruf es verwendet.
Das Token in die SPA einsetzen
Eine SPA sendet in der Regel kein klassisches Formular ab, sondern feuert einen XHR- oder fetch-Aufruf. Deshalb genügt es nicht, das Token irgendwo abzulegen – es muss genau dort landen, wo die Anwendung es beim Absenden ausliest. Drei Wege haben sich bewährt:
- das versteckte
g-recaptcha-response-Textarea füllen; - den registrierten Callback mit dem Token aufrufen;
- den XHR-Aufruf abfangen und das Token mitgeben.
Methode 1: Das versteckte Textarea füllen
token = solve_recaptcha(site_key, "https://example.com/login")
# Inject into the g-recaptcha-response textarea
page.evaluate(f"""
document.getElementById('g-recaptcha-response').value = '{token}';
""")
# Submit the form
page.click("#login-button")
Methode 2: Den Callback auslösen
Viele reCAPTCHA-Einbindungen in SPAs arbeiten mit einer Callback-Funktion, die nach erfolgreichem Lösen aufgerufen wird. Diesen Callback rufen Sie mit dem Token direkt auf:
# Find the callback function name
callback = page.evaluate("""
() => {
const widget = document.querySelector('.g-recaptcha');
return widget?.getAttribute('data-callback') || null;
}
""")
# Call it with the token
if callback:
page.evaluate(f"window['{callback}']('{token}')")
else:
# Try the default grecaptcha callback
page.evaluate(f"""
if (window.grecaptcha) {{
// Set the response and trigger verification
document.getElementById('g-recaptcha-response').value = '{token}';
// Find and call any registered callbacks
const form = document.querySelector('form');
if (form) form.dispatchEvent(new Event('submit'));
}}
""")
Methode 3: XHR abfangen (JavaScript / Puppeteer)
const puppeteer = require('puppeteer');
async function solveRecaptchaInSPA() {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
// Intercept the API call and inject the token
await page.setRequestInterception(true);
page.on('request', request => {
if (request.url().includes('/api/login')) {
const postData = JSON.parse(request.postData() || '{}');
// Token already set via page.evaluate — let it pass
request.continue();
} else {
request.continue();
}
});
await page.goto('https://example.com/login');
await page.waitForSelector('[data-sitekey]');
const siteKey = await page.$eval(
'[data-sitekey]',
el => el.getAttribute('data-sitekey')
);
// Mit CaptchaAI lösen (Implementierung ausgelassen, entspricht Python)
const token = await solveCaptcha(siteKey, page.url());
// Inject token and trigger callback
await page.evaluate((t) => {
document.getElementById('g-recaptcha-response').value = t;
const callback = document.querySelector('.g-recaptcha')
?.getAttribute('data-callback');
if (callback && window[callback]) {
window[callback](t);
}
}, token);
}
Framework-spezifische Muster
Jedes Framework rendert das Widget etwas anders:
- React mountet die Komponente asynchron;
- Vue blendet sie oft per
v-ifnach einer Interaktion ein; - Angular lädt reCAPTCHA-Module häufig verzögert (lazy) nach.
Die folgenden Snippets treffen diese typischen Fälle.
React (react-google-recaptcha)
React-Apps setzen häufig auf das Paket react-google-recaptcha. Die Komponente rendert asynchron, deshalb warten Sie auf das gemountete Element, bevor Sie den Sitekey lesen:
# Wait for React to mount the component
page.wait_for_selector(".g-recaptcha", state="attached")
# The sitekey is in the rendered div's data attribute
site_key = page.eval_on_selector(
".g-recaptcha", "el => el.dataset.sitekey"
)
Vue (vue-recaptcha)
Vue blendet das Widget oft per v-if erst nach einer Interaktion ein – lösen Sie die Bedingung aus und warten Sie dann auf den iframe:
# Vue may use v-if to conditionally render
# Navigate or interact to trigger the condition
page.click("#proceed-to-checkout")
page.wait_for_selector("iframe[src*='recaptcha']")
Angular
# Angular apps may lazy-load reCAPTCHA modules
# Wait for the specific Angular component
page.wait_for_selector("re-captcha, app-recaptcha, [data-sitekey]")
Routenwechsel abfangen
In einer SPA wird dieselbe Seite wiederverwendet: Die Navigation läuft ohne vollständiges Neuladen. Daraus folgen zwei Punkte für Ihre Automatisierung:
- Das reCAPTCHA erscheint mitten im Lebenszyklus, ohne dass ein neuer Seitenaufruf stattfindet.
- Ein
wait_for_selectorauf den iframe ist zuverlässiger als eine feste Wartezeit.
Warten Sie nach dem Routenwechsel also gezielt auf das Widget:
# Listen for reCAPTCHA appearing after SPA navigation
page.goto("https://example.com")
# Navigate within the SPA
page.click("a[href='/login']")
# The URL changed but no page reload happened
# Wait for the CAPTCHA to render in the new "page"
page.wait_for_selector("iframe[src*='recaptcha']", timeout=10000)
Fehlerbehebung
| Problem | Ursache | Lösung |
|---|---|---|
g-recaptcha-response wird nicht gefunden |
Die SPA hat das Widget noch nicht gerendert | Warten Sie mit wait_for_selector und ausreichend Timeout auf das Element |
| Token wird erzeugt, aber vom Ziel abgelehnt | sitekey, pageurl oder Session-Kontext passen nicht zusammen |
Erfassen Sie die Parameter erneut und verwenden Sie das Token in derselben Browser- oder HTTP-Sitzung |
| Token ist nach dem Einfügen wieder leer | React/Vue rendert die Komponente neu und leert das Feld | Setzen Sie das Token unmittelbar vor dem Absenden oder nutzen Sie den data-callback |
| Polling läuft in ein Timeout | Intervall, Wartezeit oder Fehlerbehandlung sind zu eng gesetzt | Fragen Sie alle 5–10 Sekunden ab, trennen Sie das Timeout von echten Fehlercodes und protokollieren Sie die Ursache |
| Läuft lokal, aber nicht im Workflow | Callback, Formularfeld oder Token-Übergabe fehlt in der echten Zielkette | Prüfen Sie den exakten Übergabepfad vom Solver bis zur finalen Zielanfrage |
Häufige Fragen
Wie finde ich den Sitekey, wenn er nur im JavaScript-Bundle steckt?
Warten Sie zuerst, bis das Framework das Widget gerendert hat, und lesen Sie den Sitekey dann aus der iframe-src (?k=…) oder dem data-sitekey-Attribut. Das Durchsuchen des gebündelten JavaScripts ist die letzte Rückfalloption – meist ist der Wert nach dem Rendern längst im DOM verfügbar.
Warum ist mein Token nach dem Einfügen plötzlich wieder leer?
Weil die Reaktivität von React oder Vue die Komponente neu rendert und das Feld dabei überschreibt. Setzen Sie das Token unmittelbar vor dem Absenden oder rufen Sie den data-callback direkt auf, statt nur das versteckte Textarea zu füllen.
Gilt derselbe Ablauf auch für reCAPTCHA v3 in einer SPA?
Im Kern ja: Sitekey und Page-URL bleiben die Grundlage. reCAPTCHA v3 liefert allerdings einen Score im Hintergrund statt einer Checkbox, sodass das Token oft schon beim Laden angefordert und an denselben API-Aufruf gehängt wird.
Was kostet das Lösen von reCAPTCHA bei CaptchaAI?
CaptchaAI rechnet pro Thread ab, nicht pro Lösung. Der Einstieg BASIC kostet 15 $/Monat mit 5 Threads und unbegrenzten Lösungen pro Thread – zusätzliche Gebühren pro CAPTCHA oder je nach Typ fallen nicht an.