Ein Apify-Actor läuft wochenlang stabil – bis eine Zielseite ein reCAPTCHA v2 einblendet und das Dataset über Nacht leer bleibt. Dafür braucht es keine neue Plattform, sondern einen zusätzlichen Schritt im requestHandler des Crawlee-Crawlers: CaptchaAI bekommt Sitekey und Page-URL, gibt das Token zurück, der Actor trägt es ein und setzt den Lauf fort. Dieser Leitfaden zeigt das komplette Setup – Eingabeschema, Actor-Code, sichere Schlüsselablage, Proxy-Aufteilung und die Frage, wie viele Threads Ihr Plan für die gewünschte Parallelität braucht.
Wie die Integration aufgebaut ist
Die Integration hat drei Aufgaben, die sauber getrennt bleiben sollten:
- Erkennen – der Handler prüft nach dem Seitenaufbau, ob ein Element mit
data-sitekeyim DOM liegt. - Lösen – Sitekey und aktuelle URL gehen an
in.php, das Ergebnis wird überres.phpabgefragt. - Fortsetzen – das Token landet im Feld
g-recaptcha-response, der Callback wird ausgelöst, das Formular wird abgesendet.
Für die Kapazitätsplanung ist eine Unterscheidung wichtig: Apify begrenzt die parallelen Browser-Sitzungen über maxConcurrency, CaptchaAI begrenzt die gleichzeitig laufenden Lösungen über die Threads Ihres Plans. Das sind zwei unabhängige Grenzen. Wer sie nicht aufeinander abstimmt, erzeugt entweder Leerlauf im Crawler oder ERROR_ZERO_BALANCE im Actor-Log.
Eingabeschema des Actors definieren
Der Actor braucht drei Eingabefelder: die Startadressen, den API-Schlüssel und die gewünschte Parallelität. isSecret: true sorgt dafür, dass der Schlüssel in der Apify-Oberfläche maskiert bleibt und nicht im Klartext in gespeicherten Läufen auftaucht.
{
"title": "CAPTCHA Scraper Input",
"type": "object",
"properties": {
"startUrls": {
"title": "Start URLs",
"type": "array",
"editor": "requestListSources"
},
"captchaaiApiKey": {
"title": "CaptchaAI API Key",
"type": "string",
"isSecret": true
},
"maxConcurrency": {
"title": "Max Concurrency",
"type": "integer",
"default": 3
}
},
"required": ["startUrls", "captchaaiApiKey"]
}
Der Standardwert 3 für maxConcurrency ist bewusst konservativ: Er passt in jeden Plan und lässt Spielraum für erneute Versuche, wenn eine Seite beim ersten Anlauf nicht durchläuft.
Crawlee-Handler: CAPTCHA erkennen und lösen
Der Actor kombiniert PlaywrightCrawler mit einer schlanken Solver-Klasse. Entscheidend ist requestHandlerTimeoutSecs: 180: Ein reCAPTCHA v2 wird in der Regel in unter 60 Sekunden gelöst, dazu kommen aber Seitenaufbau, Polling, Formularversand und Navigation.
const { Actor } = require('apify');
const { PlaywrightCrawler } = require('crawlee');
Actor.main(async () => {
const input = await Actor.getInput();
const { startUrls, captchaaiApiKey, maxConcurrency = 3 } = input;
const solver = new CaptchaAISolver(captchaaiApiKey);
const crawler = new PlaywrightCrawler({
maxConcurrency,
requestHandlerTimeoutSecs: 180,
async requestHandler({ request, page, log }) {
await page.goto(request.url, { waitUntil: 'networkidle' });
// Check for CAPTCHA
const sitekey = await page.evaluate(() => {
const el = document.querySelector('[data-sitekey]');
return el ? el.getAttribute('data-sitekey') : null;
});
if (sitekey) {
log.info(`Solving CAPTCHA on ${request.url}`);
const token = await solver.solve(sitekey, request.url);
// Inject and submit
await page.evaluate((t) => {
document.querySelector('[name="g-recaptcha-response"]').value = t;
const cb = document.querySelector('.g-recaptcha')?.getAttribute('data-callback');
if (cb && window[cb]) window[cb](t);
}, token);
await page.click('button[type="submit"]');
await page.waitForNavigation({ timeout: 15000 });
}
// Extract data
const title = await page.title();
const items = await page.$$eval('.item', els =>
els.map(el => ({
name: el.querySelector('.name')?.textContent?.trim(),
price: el.querySelector('.price')?.textContent?.trim(),
url: el.querySelector('a')?.href,
}))
);
// Push to Apify dataset
await Actor.pushData({
url: request.url,
title,
items,
scrapedAt: new Date().toISOString(),
});
log.info(`Scraped ${items.length} items from ${request.url}`);
},
});
await crawler.run(startUrls);
});
class CaptchaAISolver {
constructor(apiKey) {
this.apiKey = apiKey;
}
async solve(sitekey, pageurl) {
const params = new URLSearchParams({
key: this.apiKey,
method: 'userrecaptcha',
googlekey: sitekey,
pageurl: pageurl,
json: '1',
});
const submitResp = await fetch('https://ocr.captchaai.com/in.php', {
method: 'POST',
body: params,
});
const submitResult = await submitResp.json();
if (submitResult.status !== 1) {
throw new Error(`Submit: ${submitResult.request}`);
}
const taskId = submitResult.request;
await new Promise(r => setTimeout(r, 15000));
for (let i = 0; i < 24; i++) {
const pollResp = await fetch(
`https://ocr.captchaai.com/res.php?key=${this.apiKey}&action=get&id=${taskId}&json=1`
);
const result = await pollResp.json();
if (result.status === 1) return result.request;
if (result.request !== 'CAPCHA_NOT_READY') {
throw new Error(`Solve: ${result.request}`);
}
await new Promise(r => setTimeout(r, 5000));
}
throw new Error('Timeout');
}
}
Drei Details in diesem Code lohnen einen zweiten Blick:
- Der Callback. Viele Formulare werden erst akzeptiert, wenn die in
data-callbackhinterlegte Funktion aufgerufen wurde. Das Token nur in das versteckte Feld einzutragen, genügt dort nicht. - Die Wartezeit vor dem ersten Polling. Die Solver-Klasse wartet 15 Sekunden und fragt danach im Fünf-Sekunden-Takt ab. Das entspricht der empfohlenen Abfragefrequenz und spart überflüssige Anfragen an
res.php. - Der harte Abbruch. Nach 24 Durchläufen wirft die Klasse einen Fehler. Crawlee stellt die betroffene Anfrage anschließend erneut in die Warteschlange, statt den gesamten Lauf zu beenden.
Den API-Schlüssel als Umgebungsvariable hinterlegen
Für produktive Läufe gehört der Schlüssel nicht in die Eingabemaske, sondern in die Actor-Umgebung:
- Actor-Einstellungen → Umgebungsvariablen öffnen
CAPTCHAAI_API_KEY= Ihr Schlüssel anlegen und als geheim markieren- Im Code über
process.env.CAPTCHAAI_API_KEYzugreifen
// Alternative: use env var instead of input
const apiKey = input.captchaaiApiKey || process.env.CAPTCHAAI_API_KEY;
Der Fallback auf die Eingabe bleibt praktisch: Beim manuellen Testen setzen Sie den Schlüssel direkt im Aufruf, im geplanten Lauf zieht die Umgebungsvariable. So teilen mehrere Personen im Team denselben Actor, ohne den Schlüssel bei jedem Start mitzugeben.
Apify-Proxy und CaptchaAI kombinieren
CaptchaAI löst die CAPTCHA-Abfrage serverseitig und benötigt dafür keinen Proxy von Ihnen. Der Proxy gehört auf die Scraping-Seite, also in die Crawler-Konfiguration:
const crawler = new PlaywrightCrawler({
proxyConfiguration: await Actor.createProxyConfiguration({
groups: ['RESIDENTIAL'],
}),
// ... rest of config
});
Residential-Proxys sind deutlich teurer als Rechenzentrums-Adressen; ohne groups-Angabe greift Apify Proxy auf die günstigere Standardvariante zurück, was für viele Zielseiten ausreicht. Wer aus der EU heraus scrapt, sollte parallel die DSGVO-Frage klären: IP-Adressen gelten als personenbezogene Daten, und Rechtsgrundlage, Zweck sowie Aufbewahrungsdauer der erhobenen Daten sollten dokumentiert sein, bevor ein Actor dauerhaft im Zeitplan läuft.
Threads, Parallelität und Kosten planen
Ein Beispiel aus der Praxis: Ein Händler in Deutschland betreibt einen Shopware-Shop und lässt nachts einen Apify-Actor die eigenen Artikeldaten auf den Partnerportalen abgleichen, auf denen er gelistet ist. Rund 4.000 Seiten pro Lauf, etwa jede zwanzigste Seite zeigt ein reCAPTCHA v2 – also grob 200 Lösungen pro Nacht.
Für die Abrechnung ist diese Zahl unerheblich: CaptchaAI rechnet pro Thread ab, nicht pro Lösung, und jeder Thread löst im Abrechnungsmonat unbegrenzt viele CAPTCHAs. Maßgeblich ist allein, wie viele Lösungen gleichzeitig laufen sollen.
| Plan | Preis | Threads | Passende maxConcurrency |
|---|---|---|---|
| BASIC | 15 $/Monat | 5 | 3–5 |
| STANDARD | 30 $/Monat | 15 | bis 15 |
| ADVANCE | 90 $/Monat | 50 | bis 50 |
Der nächtliche Shopware-Abgleich kommt mit BASIC und maxConcurrency: 3 aus – die 200 Lösungen verteilen sich über Stunden, ein Engpass entsteht nicht. Erst wenn derselbe Actor tagsüber im 15-Minuten-Takt laufen soll, lohnt der Wechsel auf mehr Threads. Alle Preise sind US-Dollar-Beträge.
Häufige Fehlermeldungen im Actor-Log
| Meldung | Ursache | Vorgehen |
|---|---|---|
ERROR_WRONG_USER_KEY |
Schlüssel hat nicht das erwartete Format (32 Zeichen) | Umgebungsvariable auf Leerzeichen und Zeilenumbrüche prüfen |
ERROR_KEY_DOES_NOT_EXIST |
Schlüssel existiert nicht | Schlüssel erneut aus dem Dashboard kopieren |
ERROR_ZERO_BALANCE |
Guthaben oder freie Threads reichen nicht aus | Guthaben aufladen oder maxConcurrency senken |
ERROR_BAD_TOKEN_OR_PAGEURL |
Sitekey und Page-URL passen nicht zusammen, oft bei iframes | Sitekey aus dem tatsächlich eingebetteten Frame auslesen |
CAPCHA_NOT_READY |
Ergebnis liegt noch nicht vor | keine Aktion – im Fünf-Sekunden-Takt weiter abfragen |
ERROR_CAPTCHA_UNSOLVABLE |
mehrere Versuche ohne Ergebnis | Typ und Parameter prüfen, danach erneut einreichen |
Protokollieren Sie den Fehlertext immer über log.warning, bevor die Anfrage zurück in die Warteschlange geht. In der Apify-Oberfläche lässt sich so nach einem Lauf in Sekunden unterscheiden, ob ein Konfigurationsproblem oder nur eine schwierige Zielseite vorlag.
FAQ
Wie viele Threads brauche ich bei maxConcurrency: 10?
Im ungünstigsten Fall zehn. Jede gleichzeitig laufende Lösung belegt einen Thread, und im Extremfall zeigen alle zehn Sitzungen zur selben Zeit ein CAPTCHA. In der Praxis liegt der Bedarf niedriger, weil längst nicht jede Seite eine Abfrage einblendet – planen Sie trotzdem mit dem Maximum, wenn der Actor unbeaufsichtigt laufen soll.
Was passiert, wenn Apify den Actor mitten in einer Lösung neu startet?
Der laufende Vorgang geht verloren, der Lauf aber nicht: Crawlee legt die Anfrage zurück in die Warteschlange, und der Handler startet einen frischen Lösungsvorgang. Ein zwischengespeichertes Token wiederzuverwenden, hilft ohnehin nicht – reCAPTCHA-Tokens sind nur rund 120 Sekunden gültig.
Funktioniert das Muster auch mit dem CheerioCrawler?
Nur eingeschränkt. Der Lösungsschritt selbst ist reines HTTP und läuft überall. Für das Eintragen des Tokens und den Callback brauchen Sie jedoch eine Browser-Umgebung. Mit CheerioCrawler müssen Sie g-recaptcha-response selbst als Formularfeld in einen eigenen POST-Request packen; sobald ein JavaScript-Callback im Spiel ist, sind PlaywrightCrawler oder PuppeteerCrawler die robustere Wahl.
Zahle ich Apify und CaptchaAI getrennt?
Ja, es sind zwei getrennte Rechnungen. Apify berechnet Compute-Einheiten, Speicher und Proxy-Traffic, CaptchaAI die Threads Ihres Plans. Das CAPTCHA-Budget bleibt dadurch planbar, während die Apify-Kosten mit der Zahl der gecrawlten Seiten wachsen.
Verwandte Leitfäden
Bringen Sie die CAPTCHA-Lösung in Ihre Apify-Pipeline – zu CaptchaAI.