GeeTest v3 kommt ohne den einzelnen Sitekey aus, den man von reCAPTCHA kennt: Die Verifizierung hängt an den Werten gt und challenge, und zurück an das Formular gehen am Ende gleich drei Felder. Über die CaptchaAI-API läuft das so:
gtundchallengeaus der Seite auslesen- Aufgabe mit
method=geetestan CaptchaAI übermitteln - Ergebnis über die Task-ID abfragen
geetest_challenge,geetest_validateundgeetest_seccodemit dem Formular absenden
Für den Lösungsvorgang selbst nennt CaptchaAI eine Obergrenze von unter 12 Sekunden pro GeeTest-v3-Abfrage – der eigentliche Stolperstein in produktiven Integrationen liegt fast immer im ersten Schritt. Der Wert gt ist pro Anwendung fest verdrahtet, challenge gilt für genau eine Verifizierung. Wer diesen Unterschied überliest, baut eine Integration, die im ersten Testlauf sauber durchläuft und beim zweiten ERROR_CAPTCHA_UNSOLVABLE zurückliefert.
Dieser Artikel zeigt den vollständigen Ablauf mit Python und Node.js – samt der Fehlerbilder, die in echten Test-Pipelines auftreten.
Was Sie vorab brauchen
- CaptchaAI API-Schlüssel – aus dem Dashboard auf captchaai.com
gt-Wert – feste Kennung pro Anwendung, einmal konfiguriertchallenge-Wert – pro Sitzung neu und genau einmal gültig- Page-URL – die Adresse, auf der GeeTest eingebunden ist
- Laufzeitumgebung – Python 3.7+ oder Node.js 14+
api_server– optional, nur bei abweichendem Verifizierungsserver
Schritt 1: GeeTest-Parameter gt, challenge und api_server auslesen
Drei Parameter gehen in die Anfrage ein. gt bleibt über alle Anfragen hinweg gleich und lässt sich einmalig in die Konfiguration übernehmen; challenge müssen Sie unmittelbar vor jeder Lösung neu holen. Drei Wege führen zum Ziel – in dieser Reihenfolge sind sie am schnellsten geprüft.
Hinweis: Holen Sie
challengeimmer direkt vor der Übermittlung. Ein Wert, der dazwischen mehrere Minuten liegen bleibt, ist bei der Verifizierung meist verbraucht.
Weg 1: Netzwerk-Tab in den DevTools
- Öffnen Sie die DevTools und wechseln Sie in den Tab Netzwerk
- Filtern Sie nach
register-slide,gettype.phpoderget.php - Lösen Sie das CAPTCHA aus und suchen Sie die Initialisierungsanfrage
- Die Antwort liefert
gt,challengeund mitunterapi_server
{
"success": 1,
"gt": "019924a82c70bb123aae90d483087f94",
"challenge": "12345678abc90def12345678abc90def",
"new_captcha": true
}
Weg 2: Seitenquelltext durchsuchen
Bindet die Anwendung GeeTest direkt im Markup ein, steht gt im Initialisierungsaufruf. Ein kurzer Ausdruck in der Konsole reicht:
// Search page source for initGeetest or gt value
document.querySelectorAll('script').forEach(s => {
if (s.textContent.includes('initGeetest')) {
console.log(s.textContent);
}
});
Weg 3: Registrierungs-Endpunkt der Anwendung
Der stabilste Weg für Automatisierung: Viele Anwendungen holen die Parameter über einen eigenen Endpunkt, den Sie direkt aufrufen können – ganz ohne Browser-Rendering.
# The site's registration endpoint
params_response = requests.get("https://example.com/api/captcha/register")
data = params_response.json()
gt = data["gt"]
challenge = data["challenge"]
Schritt 2: Aufgabe an CaptchaAI übermitteln
Die Übermittlung geht an in.php mit method=geetest, zusammen mit gt, challenge und pageurl. Zurück kommt die Task-ID für den nächsten Schritt. Prüfen Sie status explizit – ein Tippfehler im gt fällt sonst erst beim Polling auf.
Python
import requests
import time
API_KEY = "YOUR_API_KEY"
response = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "geetest",
"gt": "019924a82c70bb123aae90d483087f94",
"challenge": "12345678abc90def12345678abc90def",
"api_server": "api.geetest.com", # Optional, use if site specifies
"pageurl": "https://example.com/login",
"json": 1
})
data = response.json()
if data.get("status") != 1:
raise Exception(f"Submit error: {data.get('request')}")
task_id = data["request"]
print(f"Task submitted: {task_id}")
Node.js
const axios = require('axios');
const API_KEY = 'YOUR_API_KEY';
async function submitGeeTest(gt, challenge, pageurl) {
const { data } = await axios.get('https://ocr.captchaai.com/in.php', {
params: {
key: API_KEY,
method: 'geetest',
gt,
challenge,
api_server: 'api.geetest.com',
pageurl,
json: 1
}
});
if (data.status !== 1) throw new Error(`Submit error: ${data.request}`);
return data.request;
}
Schritt 3: Ergebnis abfragen statt blind warten
Das Ergebnis holen Sie über res.php ab. Solange die Aufgabe läuft, antwortet die API mit CAPCHA_NOT_READY; jede andere Meldung ist ein echter Fehler und sollte die Schleife sofort beenden. Ein Intervall von 5 Sekunden hat sich bewährt – häufigeres Abfragen bringt bei GeeTest v3 keinen Zeitgewinn.
Zurück kommen drei Werte: geetest_challenge, geetest_validate und geetest_seccode. Der zurückgegebene challenge unterscheidet sich vom eingereichten – weitergeben müssen Sie die Version aus der Antwort.
Python
def get_geetest_solution(task_id):
for attempt in range(30):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY,
"action": "get",
"id": task_id,
"json": 1
}).json()
if result.get("status") == 1:
return result["request"]
if result.get("request") != "CAPCHA_NOT_READY":
raise Exception(f"Error: {result.get('request')}")
raise Exception("Timeout")
solution = get_geetest_solution(task_id)
# solution = {
# "geetest_challenge": "12345678abc90def12345678abc90def1a",
# "geetest_validate": "abcdef1234567890abcdef1234567890",
# "geetest_seccode": "abcdef1234567890abcdef1234567890|jordan"
# }
Node.js
async function getGeeTestSolution(taskId) {
for (let i = 0; i < 30; i++) {
await new Promise(r => setTimeout(r, 5000));
const { data } = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: taskId, json: 1 }
});
if (data.status === 1) return data.request;
if (data.request !== 'CAPCHA_NOT_READY') throw new Error(data.request);
}
throw new Error('Timeout');
}
Schritt 4: challenge, validate und seccode absenden
Alle drei Werte gehören in dieselbe Anfrage wie die übrigen Formulardaten. Fehlt einer davon, weist die Verifizierung ab – auch wenn die beiden anderen stimmen. Senden Sie außerdem zügig ab: Die Werte gelten für genau diese eine Verifizierung und lassen sich nicht auf Vorrat halten.
# Submit the GeeTest solution with the form data
verify_response = requests.post("https://example.com/api/login", data={
"username": "[email protected]",
"password": "password123",
"geetest_challenge": solution["geetest_challenge"],
"geetest_validate": solution["geetest_validate"],
"geetest_seccode": solution["geetest_seccode"]
})
print(f"Login status: {verify_response.status_code}")
Der komplette GeeTest-v3-Ablauf in einem Python-Skript
Zusammengesetzt sind es rund 30 Zeilen – kompakt genug für einen Test-Helper:
import requests
import time
API_KEY = "YOUR_API_KEY"
SITE_URL = "https://example.com/login"
# 1. Get GeeTest parameters from the site
params = requests.get("https://example.com/api/captcha/register").json()
# 2. Submit to CaptchaAI
submit = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "geetest",
"gt": params["gt"],
"challenge": params["challenge"],
"pageurl": SITE_URL,
"json": 1
}).json()
task_id = submit["request"]
# 3. Poll for solution
for _ in range(30):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": 1
}).json()
if result.get("status") == 1:
solution = result["request"]
break
# 4. Submit to site
login = requests.post(SITE_URL, data={
"username": "[email protected]",
"password": "pass",
"geetest_challenge": solution["geetest_challenge"],
"geetest_validate": solution["geetest_validate"],
"geetest_seccode": solution["geetest_seccode"]
})
print(f"Result: {login.status_code}")
Durchsatz planen: Abrechnung pro Thread
Für die Kapazitätsplanung ist eines entscheidend: CaptchaAI rechnet nach gleichzeitigen Threads ab, nicht pro gelöster Abfrage. Ein Thread ist eine laufende Lösung; sobald sie abgeschlossen ist, nimmt derselbe Thread die nächste Aufgabe an. Innerhalb eines Tarifs sind die Lösungen pro Thread unbegrenzt; abgerechnet wird in US-Dollar.
Ein typisches Rechenbeispiel aus dem DACH-Alltag: Eine nächtliche Regressionssuite läuft in GitLab CI auf einem Hetzner-VPS und prüft 400 Login-Vorgänge in der eigenen Staging-Umgebung, jeden davon mit einer GeeTest-v3-Abfrage. Rechnet man konservativ mit der Obergrenze von 12 Sekunden pro Lösung, ergeben sich rund 4.800 Sekunden Thread-Zeit; je nach Tarif verteilt sich das so:
| Tarif | Threads | Dauer des Beispiellaufs |
|---|---|---|
| BASIC (15 $/Monat) | 5 | ~16 Minuten |
| STANDARD (30 $/Monat) | 15 | ~5,3 Minuten |
| ADVANCE (90 $/Monat) | 50 | unter 2 Minuten |
Für einen nächtlichen Job genügt der kleinste Tarif also problemlos. Reserve braucht erst, wer Suiten parallel zum Tages-Traffic laufen lässt oder mehrere Projekte auf denselben Schlüssel legt.
Zwei Punkte gehören in jedes solche Setup: Testen Sie ausschließlich gegen eigene oder freigegebene Umgebungen, und behandeln Sie IP-Adressen aus Proxy- und Logdaten von Beginn an als personenbezogene Daten – die DSGVO-Bewertung gehört in die Architekturentscheidung, nicht in die Nachdokumentation.
Fehler deuten und beheben
| Fehler | Ursache | Lösung |
|---|---|---|
ERROR_BAD_PARAMETERS |
gt oder challenge fehlt oder ist leer |
Beide Werte sind Pflicht – vor dem Absenden aus der Seite auslesen |
ERROR_CAPTCHA_UNSOLVABLE |
challenge abgelaufen oder ungültig |
Frischen challenge-Wert holen und die Aufgabe erneut übermitteln |
| Anwendung weist die Lösung ab | Veralteter oder falscher challenge-Wert |
challenge ist einmalig gültig; pro Versuch einen neuen Satz verwenden |
geetest_validate ist leer |
Lösungsvorgang intern fehlgeschlagen | Mit neuem challenge wiederholen und den Fehlercode protokollieren |
| Timeout nach 30 Abfragen | Polling zu früh beendet oder Netzwerkproblem | Intervall bei 5 Sekunden belassen, Gesamtdauer erhöhen, Verbindung prüfen |
Vollständig lauffähiges Beispiel
Das Referenzprojekt bringt Umgebungskonfiguration, Polling, Wiederholungslogik und Fehlerbehandlung fertig mit.
Das vollständige ausführbare Beispiel finden Sie auf GitHub →
Häufige Fragen
Warum weist die Anwendung meine Lösung ab, obwohl CaptchaAI status: 1 meldet?
Meist wird der falsche challenge-Wert weitergereicht. Absenden müssen Sie den geetest_challenge aus der API-Antwort, nicht den ursprünglich eingereichten Wert. Zweithäufigste Ursache: ein gekürzter geetest_seccode – der Teil hinter dem Pipe-Zeichen gehört dazu.
Funktioniert derselbe Ablauf auch für GeeTest v4?
Nein. GeeTest v4 nutzt ein anderes Protokoll und wird derzeit nicht unterstützt; der Typ ist als „bald verfügbar“ angekündigt. Für v4-Abfragen ist dieses Skript daher kein gültiger Weg.
Wie viele Threads brauche ich für 5.000 GeeTest-Abfragen pro Tag?
Rechnerisch etwa einen: 5.000 Abfragen × 12 Sekunden ergeben rund 17 Stunden Thread-Zeit auf 24 Stunden verteilt. Praktisch planen Sie Reserve für Lastspitzen ein – STANDARD (30 $/Monat, 15 Threads) fängt ungleichmäßige Verteilungen ab.
Lässt sich GeeTest v3 ohne Headless-Browser lösen?
Ja, sofern Sie gt und challenge per HTTP-Anfrage erreichen. Genau dafür ist Weg 3 gedacht. Ein Browser wird erst nötig, wenn die Parameter ausschließlich nach JavaScript-Ausführung im DOM stehen.
Wie protokolliere ich Fehlversuche in der CI sinnvoll?
Vier Felder reichen, um Timeouts von echten Parameterfehlern zu trennen:
- Task-ID aus der Antwort von
in.php - Fehlercode oder letzter Status aus
res.php - Zeitstempel der Übermittlung
- Anzahl der Abfragen bis zum Abbruch
Der API-Schlüssel gehört nie ins Log, auch nicht gekürzt.