Ein verzerrtes Text-CAPTCHA lösen Sie in Node.js mit zwei HTTP-Requests: Sie schicken das Bild an in.php, fragen das Ergebnis über res.php ab und tragen den zurückgegebenen Text in das Formularfeld ein. Kein eigenes OCR-Modell, kein Tesseract-Training, keine Bildvorverarbeitung – die Erkennung übernimmt CaptchaAI, Ihr Skript kümmert sich nur um Screenshot, Übermittlung und Eingabe.
Für die Image-/OCR-Methode nennt CaptchaAI eine Lösungszeit von unter 0,5 Sekunden. Der Flaschenhals liegt also fast nie bei der Erkennung, sondern bei Ihrem Polling-Intervall und beim Seitenaufbau im Browser. Der Artikel geht den Weg der Reihe nach durch: Übermittlung, Abfrage, Parameter, Komplettbeispiel.
Wo Bild-CAPTCHAs im DACH-Alltag noch auftauchen
Klassische Text-CAPTCHAs gelten als Auslaufmodell – im deutschsprachigen Raum sind sie es nicht. Sie stecken in Behörden- und Terminportalen, in BLS-Visa-Strecken, in Mitglieder- und Vereinsverwaltungen und in Shop-Backends, die lange vor Turnstile gebaut wurden. Wer ein älteres JTL- oder Shopware-Backend testet, trifft dort weiterhin auf vier bis sechs verzerrte Zeichen.
Ein realistisches Szenario: Ein nächtlicher Job auf einem Hetzner-Server meldet sich an einem Lieferantenportal an, das vor dem Login ein Rasterbild mit Ziffern zeigt. Um 03:00 Uhr sitzt im GitLab-CI-Container niemand daneben – die OCR-API füllt genau diese Lücke.
Zwei Randbedingungen, die in DACH-Projekten regelmäßig aufschlagen:
- Automatisieren Sie nur Portale, die Ihnen gehören oder für die Sie eine schriftliche Freigabe haben. Bei Fremdsystemen sind Nutzungsbedingungen und Zugriffsberechtigung die erste Prüfung, nicht der Code.
- Screenshots von Formularseiten können personenbezogene Daten enthalten. Schreiben Sie
captcha.pngin ein temporäres Verzeichnis, räumen Sie es nach dem Lauf ab und protokollieren Sie keine vollständigen Seitenabzüge – das erspart Ihnen im DSGVO-Kontext unnötige Diskussionen.
Voraussetzungen
| Baustein | Anforderung |
|---|---|
| API-Schlüssel | Aus Ihrem Konto auf captchaai.com |
| Node.js | ab Version 14 |
| Pakete | axios, fs – für Variante B zusätzlich form-data |
| Bildformat | JPG, PNG oder GIF, 100 Byte bis 100 KB |
Die Endpunkte sind in beiden Varianten dieselben: in.php für die Übermittlung, res.php für die Abholung, beide unter ocr.captchaai.com. YOUR_API_KEY kommt produktiv aus einer Umgebungsvariablen, nicht als Literal ins Repository.
Variante A: Bild als Base64 übermitteln
Base64 ist der Standardweg, wenn das Bild ohnehin im Speicher liegt – etwa als Puppeteer-Screenshot. Sie kodieren es, hängen es als body-Parameter an und bekommen eine Task-ID zurück.
const axios = require('axios');
const fs = require('fs');
const API_KEY = 'YOUR_API_KEY';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// Read and encode the image
const imageB64 = fs.readFileSync('captcha.png').toString('base64');
// Submit to CaptchaAI
const { data: submitData } = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: API_KEY,
method: 'base64',
body: imageB64,
json: 1,
},
});
if (submitData.status !== 1) throw new Error(submitData.request);
const taskId = submitData.request;
console.log(`Task submitted: ${taskId}`);
Der Rückgabewert status: 1 bedeutet: Auftrag angenommen, request enthält die Task-ID. Bei status: 0 steht in request der Fehlercode – behandeln Sie diesen Fall sofort, sonst pollen Sie später auf eine ID, die es nie gab.
Variante B: Datei-Handle statt Base64
Bei größeren Bildern oder wenn die Datei bereits auf der Platte liegt, sparen Sie sich mit multipart/form-data den Kodierungsschritt und rund ein Drittel Übertragungsvolumen.
const FormData = require('form-data');
const form = new FormData();
form.append('key', API_KEY);
form.append('method', 'post');
form.append('json', '1');
form.append('file', fs.createReadStream('captcha.png'));
const { data: submitData } = await axios.post('https://ocr.captchaai.com/in.php', form, {
headers: form.getHeaders(),
});
const taskId = submitData.request;
Beide Wege sind gleichwertig, die Antwortstruktur ist identisch. Entscheiden Sie nach Datenfluss: Speicher zu Base64, Datei zu Upload.
Ergebnis abfragen statt blind warten
Die Übermittlung liefert nur die Task-ID. Den erkannten Text holen Sie über res.php ab – mit action=get und der ID. Solange die Lösung noch läuft, antwortet die API mit CAPCHA_NOT_READY (die API schreibt es tatsächlich ohne „T“, das ist kein Tippfehler in diesem Artikel).
await sleep(5000);
let captchaText;
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) {
captchaText = pollData.request;
console.log(`CAPTCHA text: ${captchaText}`);
break;
}
if (pollData.request !== 'CAPCHA_NOT_READY') {
throw new Error(pollData.request);
}
await sleep(5000);
}
Die Schleife bricht bei jedem anderen Fehlerwert kontrolliert ab, statt 30-mal ins Leere zu laufen. Für Bild-CAPTCHAs reicht meist der erste Durchlauf; der 5-Sekunden-Puffer ist bewusst konservativ. Wer viele Aufträge parallel fährt, sollte das Intervall pro Auftrag leicht streuen, damit die Abfragen nicht im Gleichtakt einschlagen.
Erkennung mit Parametern eingrenzen
Die Trefferquote steigt spürbar, sobald die API weiß, was sie erwarten darf. Vier Ziffern sind ein anderer Suchraum als eine Rechenaufgabe mit Vorzeichen.
// Digits only, 4-6 characters
const { data } = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: API_KEY,
method: 'base64',
body: imageB64,
numeric: 1, // digits only
min_len: 4, // minimum length
max_len: 6, // maximum length
json: 1,
},
});
| Parameter | Wert | Wirkung |
|---|---|---|
numeric |
1 = nur Ziffern, 2 = nur Buchstaben |
schränkt den Zeichenvorrat ein |
min_len / max_len |
Ganzzahl | erzwingt eine Längenspanne |
calc |
1 |
wertet eine Rechenaufgabe aus und liefert das Ergebnis |
regsense |
1 |
Groß- und Kleinschreibung wird beachtet |
Setzen Sie diese Werte nur, wenn Sie das Muster wirklich kennen. Ein zu enges max_len verwirft eine korrekte Erkennung, die schlicht ein Zeichen länger war.
Komplettbeispiel: Bild-CAPTCHA per Puppeteer-Screenshot lösen
Das folgende Skript verbindet alle Schritte: Seite laden, CAPTCHA-Element abfotografieren, übermitteln, Ergebnis abfragen, Text eintragen und Formular absenden.
const axios = require('axios');
const puppeteer = require('puppeteer');
const fs = require('fs');
const API_KEY = 'YOUR_API_KEY';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function solveImageCaptcha() {
// 1. Load page and screenshot CAPTCHA
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/register');
const captchaEl = await page.$('#captcha-image');
await captchaEl.screenshot({ path: 'captcha.png' });
// 2. Encode and submit
const imageB64 = fs.readFileSync('captcha.png').toString('base64');
const { data: submit } = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: { key: API_KEY, method: 'base64', body: imageB64, json: 1 },
});
const taskId = submit.request;
// 3. Poll for text
await sleep(5000);
let text;
for (let i = 0; i < 30; i++) {
const { data: poll } = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: taskId, json: 1 },
});
if (poll.status === 1) { text = poll.request; break; }
if (poll.request !== 'CAPCHA_NOT_READY') throw new Error(poll.request);
await sleep(5000);
}
// 4. Type and submit
await page.type('#captcha-input', text);
await page.click('form [type="submit"]');
console.log(`Solved: ${text}`);
await browser.close();
}
solveImageCaptcha().catch(console.error);
Erwartete Ausgabe:
Solved: ABC123
Für den Dauerbetrieb fehlen zwei Dinge: ein try/finally-Block, damit der Browser auch nach einem Fehler geschlossen wird, und ein zweiter Versuch mit frisch geladenem Bild, falls das Formular den Text ablehnt.
Fehlercodes richtig lesen
| Fehler | Ursache | Lösung |
|---|---|---|
ERROR_WRONG_FILE_EXTENSION |
Format wird nicht unterstützt | JPG, PNG oder GIF verwenden |
ERROR_TOO_BIG_CAPTCHA_FILESIZE |
Bild größer als 100 KB | vor dem Senden komprimieren |
ERROR_ZERO_CAPTCHA_FILESIZE |
Bild kleiner als 100 Byte | Screenshot-Selektor prüfen |
CAPCHA_NOT_READY |
Lösung läuft noch | weiter abfragen, Intervall 5 Sekunden |
Der häufigste Praxisfehler steht nicht in dieser Tabelle: ein leeres Bild, weil der Selektor #captcha-image zum Zeitpunkt des Screenshots noch nicht gerendert war. Warten Sie mit page.waitForSelector auf das Element, bevor Sie es abfotografieren.
Durchsatz: Abrechnung pro Thread, nicht pro Lösung
CaptchaAI rechnet nach gleichzeitigen Threads ab, nicht pro gelöstem CAPTCHA. Ein Thread ist ein Auftrag in Bearbeitung; sobald er fertig ist, nimmt er den nächsten. Innerhalb des Tarifs sind die Lösungen pro Thread unbegrenzt, es gibt keine Tagesgrenze und keinen Aufschlag nach CAPTCHA-Typ.
Für Bild-CAPTCHAs heißt das: Ein einzelner Cronjob mit sequenzieller Verarbeitung ist bereits mit BASIC (15 $ pro Monat, 5 Threads) abgedeckt. Erst wenn mehrere Worker parallel laufen – etwa je ein Prozess pro Mandant oder Testumgebung – lohnt der Sprung auf ADVANCE (90 $ pro Monat, 50 Threads). Die Preise werden in US-Dollar abgerechnet; die aktuelle Übersicht steht auf der Preisseite von CaptchaAI.
Häufige Fragen
Wie schnell liefert die OCR-API den Text zurück?
Für Bild-CAPTCHAs nennt CaptchaAI eine Lösungszeit von unter 0,5 Sekunden bei hoher Erfolgsquote. Die gefühlte Dauer bestimmt Ihr Polling-Intervall: Wer starr 5 Sekunden wartet, wartet meist länger als nötig.
Was mache ich, wenn der erkannte Text falsch war?
Melden Sie die Task-ID über res.php mit action=reportbad zurück – das ist der sauberere Weg als eine stille Wiederholung im Skript.
Kann ich damit auch reCAPTCHA oder hCaptcha aus einem Screenshot lösen?
Nein. Die OCR-Methode ist für verzerrte Text- und Rasterbilder gedacht. reCAPTCHA v2/v3, Cloudflare Turnstile und GeeTest v3 haben eigene Methoden mit Sitekey und Page-URL statt Bildupload. hCaptcha und FunCaptcha (Arkose Labs) unterstützt CaptchaAI nicht, GeeTest v4 ist als bald verfügbar angekündigt.
Funktioniert der Ablauf ohne Browser, etwa in einem Cronjob?
Ja. Puppeteer ist nur die bequemste Art, an das Bild zu kommen. Wenn Sie die CAPTCHA-Grafik per axios direkt herunterladen können, reichen axios und fs – das Skript läuft dann auch in einem schlanken Alpine-Container ohne Chromium.
Wie viele Bild-CAPTCHAs kann ich gleichzeitig verarbeiten?
So viele, wie Ihr Tarif Threads hat: Fünf Threads bedeuten fünf Aufträge gleichzeitig in Bearbeitung. Die Warteschlange davor verwalten Sie selbst, etwa mit einem Semaphor vor dem in.php-Aufruf.
Weiterführende Anleitungen
Jetzt API-Schlüssel holen und Bild-CAPTCHAs automatisch auslesen →