Ein Rasterbild-CAPTCHA lösen Sie aus Node.js heraus in vier Schritten: Raster als Screenshot erfassen, Bild und Anweisungstext an die API übermitteln, das Ergebnis abfragen und die zurückgegebenen Kachelnummern anklicken. Eine eigene Bilderkennung brauchen Sie dafür nicht – CaptchaAI liefert die zu klickenden Zellen als JSON-Array zurück, Puppeteer übernimmt den Browser-Teil.
Grid Image gehört zu den regulär unterstützten CAPTCHA-Typen des Dienstes; die öffentlich angegebene Lösungszeit liegt bei unter einer Sekunde, unabhängig vom Schwierigkeitsgrad des Rasters. Der Engpass liegt in der Praxis deshalb selten bei der Erkennung, sondern im Browser: das richtige iframe finden, das Raster sauber erfassen und die Kacheln in der erwarteten Reihenfolge anklicken. Genau diese drei Stellen behandelt dieses Tutorial.
So läuft die Lösung eines Rasterbild-CAPTCHAs ab
Der Ablauf ist immer derselbe, egal ob 3×3 oder 4×4:
- Erfassen – Puppeteer wechselt in das Challenge-iframe, liest die Anweisung („Wählen Sie alle Bilder mit Ampeln aus“) und erstellt einen Screenshot des Rasters.
- Übermitteln – Bild, Rastergröße und Anweisungstext gehen per Multipart-Upload an
in.php. - Abfragen – Der Status wird über
res.phpabgefragt, bis das Ergebnis vorliegt. - Anwenden – Die Antwort ist eine Liste von Zellennummern; die Automatisierung klickt genau diese Kacheln.
Die Nummerierung beginnt bei 1 und läuft zeilenweise von links oben nach rechts unten: Bei 3×3 ist Zelle 1 oben links, Zelle 9 unten rechts – im Code deshalb der Zugriff über cellNum - 1.
Voraussetzungen
| Position | Wert |
|---|---|
| CaptchaAI API-Schlüssel | Aus dem Konto auf captchaai.com |
| Node.js | 14+ |
| Bibliotheken | axios, puppeteer |
Zusätzlich benötigt wird form-data für den Multipart-Upload. Auf Servern ohne Desktop-Umgebung – etwa einem schlanken VPS – installieren Sie vorab die Chromium-Systembibliotheken, sonst startet der Headless-Browser nicht.
Schritt 1: Das Rasterbild im Challenge-iframe erfassen
reCAPTCHA rendert die Bildauswahl nicht im Hauptdokument, sondern in einem eigenen iframe mit recaptcha/api2/bframe in der URL. Der Screenshot muss deshalb aus diesem Frame kommen, nicht aus der Seite selbst. Ebenso wichtig ist der Anweisungstext: Er entscheidet darüber, wonach überhaupt gesucht wird, und wird im nächsten Schritt mitgeschickt.
const puppeteer = require('puppeteer');
const fs = require('fs');
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/page-with-recaptcha');
// Switch to the reCAPTCHA challenge iframe
const frames = page.frames();
const challengeFrame = frames.find((f) => f.url().includes('recaptcha/api2/bframe'));
// Get the instruction text
const instruction = await challengeFrame.$eval(
'.rc-imageselect-desc-no-canonical',
(el) => el.textContent.trim()
);
// Screenshot the grid
const grid = await challengeFrame.$('.rc-imageselect-target');
await grid.screenshot({ path: 'grid.png' });
Schritt 2: Raster und Anweisung an CaptchaAI übermitteln
Der Upload geht als Multipart-Formular an in.php. Entscheidend sind drei Felder: grid_size beschreibt das Raster (3x3 oder – bei den größeren Varianten – 4x4), img_type gibt die Quelle an, und instructions transportiert den vorher ausgelesenen Anweisungstext. Ohne diesen Text weiß die Erkennung nicht, welche Objekte gesucht werden.
Mit json: 1 antwortet die API strukturiert; die Task-ID steht im Feld request. Der API-Schlüssel gehört in eine Umgebungsvariable – YOUR_API_KEY ist nur ein Platzhalter.
const axios = require('axios');
const FormData = require('form-data');
const API_KEY = 'YOUR_API_KEY';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const form = new FormData();
form.append('key', API_KEY);
form.append('method', 'post');
form.append('grid_size', '3x3');
form.append('img_type', 'recaptcha');
form.append('instructions', instruction);
form.append('json', '1');
form.append('file', fs.createReadStream('grid.png'));
const { data: submitData } = await axios.post('https://ocr.captchaai.com/in.php', form, {
headers: form.getHeaders(),
});
if (submitData.status !== 1) throw new Error(submitData.request);
const taskId = submitData.request;
console.log(`Task submitted: ${taskId}`);
Schritt 3: Das Ergebnis abfragen
Das Polling läuft gegen res.php. Die erste Abfrage erfolgt bewusst erst nach einer kurzen Wartezeit, danach im Fünf-Sekunden-Takt. Solange die Aufgabe in Arbeit ist, antwortet die API mit CAPCHA_NOT_READY – man beachte die Schreibweise ohne „T“, das ist kein Tippfehler im Code, sondern der tatsächliche Wert der API. Jede andere Antwort ist ein Fehlercode und sollte die Schleife sofort beenden, statt sie leerlaufen zu lassen.
Die Obergrenze von 30 Durchläufen ist ein Sicherheitsnetz: Sie verhindert, dass ein hängender Task einen Worker dauerhaft blockiert.
await sleep(5000);
let cellsToClick;
for (let i = 0; i < 30; i++) {
const { data: pollData } = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: taskId, json: 1 },
});
if (pollData.status === 1) {
cellsToClick = JSON.parse(pollData.request);
console.log('Click cells:', cellsToClick);
break;
}
if (pollData.request !== 'CAPCHA_NOT_READY') {
throw new Error(pollData.request);
}
await sleep(5000);
}
Schritt 4: Die richtigen Kacheln anklicken
Die Antwort ist ein Array wie [1, 3, 6, 9]. Diese Nummern werden auf die Kachel-Elemente im iframe abgebildet und nacheinander angeklickt. Die kurze Pause von 300 Millisekunden zwischen den Klicks sorgt dafür, dass die Oberfläche jeden Klick registriert, bevor der nächste folgt – ohne sie gehen bei langsamen Verbindungen einzelne Kacheln verloren. Zum Schluss bestätigt der Verify-Button die Auswahl.
const tiles = await challengeFrame.$$('.rc-imageselect-tile');
for (const cellNum of cellsToClick) {
await tiles[cellNum - 1].click();
await sleep(300);
}
// Click verify
await challengeFrame.click('#recaptcha-verify-button');
console.log(`Solved: clicked tiles ${JSON.stringify(cellsToClick)}`);
await browser.close();
Ein erfolgreicher Durchlauf gibt auf der Konsole aus:
Click cells: [1, 3, 6, 9]
Solved: clicked tiles [1,3,6,9]
Praxisbeispiel: nächtliche Regressionstests einer Anmeldestrecke
Ein Münchner SaaS-Team testet jede Nacht die eigene Registrierungsstrecke auf https://staging.example-app.test/registrierung. Die Staging-Umgebung spiegelt die Produktion inklusive aktiver reCAPTCHA-Bildauswahl, weil genau diese Abfrage schon Anmeldungen blockiert hat.
Der Node.js-Worker läuft dafür auf einem kleinen Hetzner-VPS in Nürnberg, gestartet von einem nächtlichen GitLab-CI-Job – in deutschen Teams die verbreitetere Kombination. Der API-Schlüssel liegt als maskierte CI/CD-Variable vor, nicht im Repository.
Wichtig: grid.png wird nach jedem Lauf gelöscht. Test-Artefakte wandern sonst in langlebige CI-Caches – bei personenbezogenen Testdaten ein unnötiges DSGVO-Risiko, das eine Zeile Aufräumcode vermeidet. Für Shopware- oder JTL-Projekte gilt derselbe Aufbau: eigene Staging-Instanz, eigene Testdaten, keine fremden Zielsysteme.
Threads statt Einzelabrechnung: Kapazität planen
CaptchaAI rechnet nach gleichzeitigen Threads ab, nicht pro gelöstem CAPTCHA. Ein Thread ist eine laufende Abfrage; sobald sie fertig ist, nimmt derselbe Thread die nächste. Innerhalb eines Tarifs sind die Lösungen pro Thread unbegrenzt.
| Tarif | Preis pro Monat | Threads |
|---|---|---|
| BASIC | 15 $ | 5 |
| STANDARD | 30 $ | 15 |
| ADVANCE | 90 $ | 50 |
Preise in US-Dollar; die Tarife reichen bis VIP-3. Entscheidend ist damit nur, wie viele Rasterabfragen bei Ihnen gleichzeitig offen sind: Ein CI-Lauf mit vier parallelen Browser-Instanzen kommt mit BASIC (15 $, 5 Threads) aus. Erst bei dauerhaft Dutzenden parallelen Workern lohnt sich ADVANCE (90 $, 50 Threads).
Typische Fehlerquellen
| Symptom | Ursache | Abhilfe |
|---|---|---|
| Screenshot ist leer oder weiß | Das Raster war beim Screenshot noch nicht gerendert | Auf den Selektor .rc-imageselect-target warten, bevor Sie den Screenshot auslösen |
challengeFrame ist undefined |
Das Bild-iframe existiert noch nicht | Erst den CAPTCHA-Checkbox-Klick auslösen, dann die Frame-Liste erneut lesen |
| Antwort passt nicht zum Bild | Der Anweisungstext fehlte oder war leer | instructions immer mitschicken und vorher auf Inhalt prüfen |
| Klicks landen auf der falschen Kachel | Off-by-one bei der Nummerierung | Zellennummern beginnen bei 1, Array-Indizes bei 0 |
| Schleife läuft 30-mal ins Leere | Fehlercode statt CAPCHA_NOT_READY |
Jede unbekannte Antwort als Fehler behandeln und abbrechen |
Häufige Fragen
Wie schnell wird ein Rasterbild-CAPTCHA gelöst?
Die öffentlich angegebene Lösungszeit für Grid Image liegt bei unter einer Sekunde, unabhängig vom Schwierigkeitsgrad. Im Gesamtlauf wiegt der Browser-Teil schwerer: Frame-Wechsel, Screenshot und Klickpausen brauchen meist länger als die API-Antwort.
Was kostet das Lösen von Rasterbild-CAPTCHAs?
Es gibt keinen Aufpreis pro Typ und keine Abrechnung pro Lösung. Sie zahlen den Monatstarif für eine Anzahl gleichzeitiger Threads, die Lösungen pro Thread sind unbegrenzt. Der Einstieg liegt bei BASIC mit 15 $ pro Monat und 5 Threads.
Löst CaptchaAI auch die Bildraster von hCaptcha?
Nein. hCaptcha wird nicht unterstützt, ebenso wenig FunCaptcha; GeeTest v4 ist als „bald verfügbar“ angekündigt. Unterstützt sind unter anderem reCAPTCHA v2 und v3, Cloudflare Turnstile, GeeTest v3 sowie Bild- und Rasterbild-CAPTCHAs. Dieses Tutorial bezieht sich auf das reCAPTCHA-Bildraster.
Das Raster lädt nach dem Klicken neue Kacheln nach – was tun?
Dann erfassen und übermitteln Sie erneut – manche Varianten tauschen nach dem Klick einzelne Kacheln aus, statt die Auswahl zu bestätigen. Kapseln Sie die Schritte 1 bis 4 in eine Schleife mit maximal drei Runden: nach dem Verify-Klick prüfen, ob das Raster noch sichtbar ist, und gegebenenfalls neu erfassen.
Funktioniert das auch mit Playwright?
Ja. Nur der Browser-Teil ändert sich – die Frame-Auswahl und die Screenshot-Methode heißen dort anders. Die API-Aufrufe an in.php und res.php sowie die Verarbeitung der Zellennummern bleiben identisch.
Weiterführende Artikel
API-Schlüssel holen und das erste Rasterbild-CAPTCHA lösen →