Anwendungsfälle

Puppeteer-CAPTCHA-Lösung mit Node.js und CaptchaAI

Puppeteer füllt Formulare aus und rendert JavaScript – ein reCAPTCHA v2 oder ein Cloudflare Turnstile löst der Browser aber nicht selbst. Die Aufgabenteilung ist einfach: Puppeteer liest den Sitekey aus dem DOM, die CaptchaAI-API löst die Abfrage serverseitig und gibt ein Token zurück, und Puppeteer trägt es ins Formular ein. Diese Trennung hält Ihr Skript schlank – die eigentliche Erkennung läuft außerhalb des Browsers.

Dieser Leitfaden führt durch den kompletten Node.js-Ablauf: vom wiederverwendbaren Solver-Modul über die Browser-Konfiguration bis zum vollständigen Scraping-Skript samt Fehlerbehandlung. Die Beispiele decken reCAPTCHA v2 und Cloudflare Turnstile ab; für andere Typen ändern sich nur die API-Parameter.

Voraussetzungen

Anforderung Einzelheiten
Node.js 16+ Mit npm
Puppeteer npm install puppeteer
Axios npm install axios
CaptchaAI API-Schlüssel Von captchaai.com

So läuft die Integration ab

Der Ablauf ist bei jedem CAPTCHA-Typ gleich – nur die API-Parameter unterscheiden sich:

  1. Puppeteer öffnet die Seite mit dem CAPTCHA
  2. Ihr Skript liest den Sitekey aus dem DOM aus
  3. CaptchaAI löst die Abfrage serverseitig und liefert ein Token
  4. Ihr Skript trägt das Token ein und sendet das Formular ab

Welche CAPTCHA-Typen deckt CaptchaAI ab?

Bevor Sie integrieren, lohnt der Blick auf die unterstützten Typen. Generell verfügbar sind reCAPTCHA v2 und v3 (inklusive Enterprise), Cloudflare Turnstile und Cloudflare Challenge, GeeTest v3 sowie Bild-, Raster- und BLS-CAPTCHAs. CaptchaFox, Friendly Captcha und Lemin befinden sich in der Beta-Phase.

Zwei Punkte bewahren Ihr Skript vor falschen Annahmen: hCaptcha und FunCaptcha (Arkose Labs) werden nicht unterstützt, und GeeTest v4 ist als „bald verfügbar“ angekündigt, aber noch nicht nutzbar. Für alle unterstützten Typen bleibt der Ablauf identisch – nur method und der Sitekey-Parameter wechseln.

Schritt 1: Solver-Modul erstellen

Die API-Aufrufe kapseln Sie in ein eigenes Modul: Aufgabe an in.php, Ergebnis per Polling von res.php.

// solver.js
const axios = require("axios");

const API_KEY = "YOUR_API_KEY";
const POLL_INTERVAL = 5000;
const MAX_ATTEMPTS = 60;

async function solveRecaptchaV2(siteKey, pageUrl) {
  // Submit task
  const submitResp = await axios.get("https://ocr.captchaai.com/in.php", {
    params: {
      key: API_KEY,
      method: "userrecaptcha",
      googlekey: siteKey,
      pageurl: pageUrl,
    },
  });

  if (!submitResp.data.startsWith("OK|")) {
    throw new Error(`Submit failed: ${submitResp.data}`);
  }

  const taskId = submitResp.data.split("|")[1];
  console.log(`Task submitted: ${taskId}`);

  // Poll for result
  for (let i = 0; i < MAX_ATTEMPTS; i++) {
    await new Promise((r) => setTimeout(r, POLL_INTERVAL));

    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: taskId },
    });

    if (result.data === "CAPCHA_NOT_READY") continue;
    if (result.data.startsWith("OK|")) {
      return result.data.split("|")[1];
    }
    throw new Error(`Solve failed: ${result.data}`);
  }
  throw new Error("Solve timed out");
}

async function solveTurnstile(siteKey, pageUrl) {
  const submitResp = await axios.get("https://ocr.captchaai.com/in.php", {
    params: {
      key: API_KEY,
      method: "turnstile",
      sitekey: siteKey,
      pageurl: pageUrl,
    },
  });

  if (!submitResp.data.startsWith("OK|")) {
    throw new Error(`Submit failed: ${submitResp.data}`);
  }

  const taskId = submitResp.data.split("|")[1];

  for (let i = 0; i < MAX_ATTEMPTS; i++) {
    await new Promise((r) => setTimeout(r, POLL_INTERVAL));
    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: taskId },
    });
    if (result.data === "CAPCHA_NOT_READY") continue;
    if (result.data.startsWith("OK|")) return result.data.split("|")[1];
    throw new Error(`Solve failed: ${result.data}`);
  }
  throw new Error("Solve timed out");
}

module.exports = { solveRecaptchaV2, solveTurnstile };

reCAPTCHA erwartet dabei googlekey, Turnstile erwartet sitekey.

Schritt 2: Puppeteer-Browser konfigurieren

Starten Sie den Browser mit realistischem User-Agent:

const puppeteer = require("puppeteer");

async function createBrowser() {
  const browser = await puppeteer.launch({
    headless: "new",
    args: [
      "--no-sandbox",
      "--disable-setuid-sandbox",
      "--disable-blink-features=AutomationControlled",
    ],
  });

  const page = await browser.newPage();
  await page.setUserAgent(
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
  );

  return { browser, page };
}

Schritt 3: reCAPTCHA auf einer Seite lösen

Seite laden, data-sitekey aus dem .g-recaptcha-Element auslesen, an CaptchaAI übergeben und das Token ins versteckte Feld g-recaptcha-response schreiben:

const { solveRecaptchaV2 } = require("./solver");

async function scrapeWithCaptcha(url) {
  const { browser, page } = await createBrowser();

  try {
    await page.goto(url, { waitUntil: "networkidle2" });

    // Extract site key
    const siteKey = await page.$eval(
      ".g-recaptcha",
      (el) => el.getAttribute("data-sitekey")
    );
    console.log("Site key:", siteKey);

    // Mit CaptchaAI lösen
    const token = await solveRecaptchaV2(siteKey, url);
    console.log("Token received:", token.substring(0, 50));

    // Inject token
    await page.evaluate((token) => {
      document.getElementById("g-recaptcha-response").innerHTML = token;
      document.getElementById("g-recaptcha-response").style.display = "";
    }, token);

    // Submit the form
    await page.click('button[type="submit"]');
    await page.waitForNavigation({ waitUntil: "networkidle2" });

    // Scrape the content
    const content = await page.content();
    console.log("Page loaded successfully");
    return content;
  } finally {
    await browser.close();
  }
}

Schritt 4: Callbacks statt Formular-Submit behandeln

Manche Seiten rufen statt eines Formular-Submits eine JavaScript-Callback-Funktion auf, sobald das Token vorliegt. Dann lösen Sie den Callback direkt im Seitenkontext aus:

// Trigger the reCAPTCHA callback
await page.evaluate((token) => {
  // Method 1: Direct callback
  if (typeof ___grecaptcha_cfg !== "undefined") {
    const clients = ___grecaptcha_cfg.clients;
    Object.keys(clients).forEach((key) => {
      const client = clients[key];
      // Find the callback function
      const findCallback = (obj) => {
        for (const prop in obj) {
          if (typeof obj[prop] === "function") {
            obj[prop](token);
            return true;
          }
          if (typeof obj[prop] === "object" && obj[prop] !== null) {
            if (findCallback(obj[prop])) return true;
          }
        }
        return false;
      };
      findCallback(client);
    });
  }
}, token);

Vollständiges Praxisbeispiel

Alle Schritte in einer lauffähigen Datei – von page.goto bis zum abgesendeten Formular:

const puppeteer = require("puppeteer");
const axios = require("axios");

const API_KEY = "YOUR_API_KEY";

async function solveCaptcha(siteKey, pageUrl) {
  const submit = await axios.get("https://ocr.captchaai.com/in.php", {
    params: {
      key: API_KEY,
      method: "userrecaptcha",
      googlekey: siteKey,
      pageurl: pageUrl,
    },
  });
  const taskId = submit.data.split("|")[1];

  while (true) {
    await new Promise((r) => setTimeout(r, 5000));
    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: taskId },
    });
    if (result.data === "CAPCHA_NOT_READY") continue;
    if (result.data.startsWith("OK|")) return result.data.split("|")[1];
    throw new Error(result.data);
  }
}

(async () => {
  const browser = await puppeteer.launch({
    headless: "new",
    args: ["--disable-blink-features=AutomationControlled"],
  });
  const page = await browser.newPage();

  try {
    await page.goto("https://example.com/login", {
      waitUntil: "networkidle2",
    });

    // Get the site key
    const siteKey = await page.$eval(".g-recaptcha", (el) =>
      el.getAttribute("data-sitekey")
    );

    // Solve
    const token = await solveCaptcha(siteKey, page.url());

    // Inject and submit
    await page.evaluate((t) => {
      document.getElementById("g-recaptcha-response").innerHTML = t;
    }, token);

    await page.click("#submit-btn");
    await page.waitForNavigation();

    console.log("Done:", page.url());
  } finally {
    await browser.close();
  }
})();

Wiederholungslogik und Timeouts

Im Produktivbetrieb scheitert gelegentlich eine einzelne Anfrage – ein Netzwerk-Timeout, eine kurzzeitig überlastete Zielseite oder ein Token, das zu spät eingetroffen ist. Fangen Sie das mit einer knappen Wiederholungslogik ab: Umschließen Sie den Solver-Aufruf mit einem try/catch und wiederholen Sie ihn maximal zwei- bis dreimal, idealerweise mit exponentiellem Backoff (etwa 2, 4 und 8 Sekunden Wartezeit).

Zwei Zahlen sollten Sie dabei im Blick behalten. Das Polling-Intervall aus dem Solver-Modul liegt bei 5 Sekunden; das reicht für die meisten Typen, da Cloudflare Turnstile in der Regel in unter 10 Sekunden und reCAPTCHA v2 in unter 60 Sekunden gelöst wird. Und ein gelöstes Token bleibt nur rund 120 Sekunden gültig – lösen Sie also erst kurz vor dem Absenden und tragen Sie das Ergebnis sofort ein.

Praxis-Szenario: Scraping-Worker in der DACH-Region

Typischer Aufbau: mehrere parallele Puppeteer-Instanzen auf einem VPS – etwa bei Hetzner oder netcup –, jede stößt beim Login auf ein reCAPTCHA. Zur Kapazität: Bei CaptchaAI zählen gleichzeitige Threads, nicht die Zahl der Lösungen. BASIC (15 $/Monat, 5 Threads) deckt fünf parallele Läufe ab, STANDARD (30 $/Monat, 15 Threads) mehr. Mehrere CAPTCHAs eines Laufs bündeln Sie mit Promise.all().

Zum rechtlichen Rahmen: Beim Scraping gelten IP-Adressen nach DSGVO bereits als personenbezogen. Prüfen Sie vor dem Produktivlauf Ihre Rechtsgrundlage und die Nutzungsbedingungen der Zielseite. Preise gelten in US-Dollar.

Fehlerbehebung

Problem Ursache Lösung
page.$eval schlägt fehl CAPTCHA wird nachgeladen page.waitForSelector('.g-recaptcha') einsetzen
Token wird abgelehnt Vor dem Absenden abgelaufen Token sofort nach Erhalt einfügen
Website reagiert nicht auf Puppeteer Browser-Flags fehlen --disable-blink-features=AutomationControlled ergänzen
Navigation timeout Kein Seitenwechsel nach Submit Prüfen, ob die Seite AJAX statt Formular-Post nutzt

Häufige Fragen

Welchen CaptchaAI-Tarif brauche ich für paralleles Puppeteer-Scraping?

Entscheidend ist die Zahl gleichzeitiger Lösungen, nicht das Volumen. Für fünf parallele Worker genügt BASIC (15 $/Monat, 5 Threads); jeder Thread löst im Abrechnungsmonat unbegrenzt viele CAPTCHAs.

Wie lange ist ein gelöstes reCAPTCHA-Token gültig?

Etwa 120 Sekunden. Starten Sie den Solver-Aufruf daher erst kurz vor dem Absenden und schreiben Sie das Token sofort nach Erhalt ins Feld g-recaptcha-response. Token auf Vorrat zu halten funktioniert nicht.

Funktioniert Puppeteer im Headless-Modus mit CaptchaAI?

Ja. Weil CaptchaAI serverseitig löst, spielt es keine Rolle, ob der Browser sichtbar läuft. Headless ist im Server-Betrieb der Normalfall; den sichtbaren Modus brauchen Sie nur zum Debuggen.

Puppeteer oder Playwright für das CAPTCHA-Handling?

Für die Integration sind beide gleichwertig – Sitekey auslesen, serverseitig lösen, Token einfügen läuft identisch. Nehmen Sie das Framework, das ohnehin in Ihrem Stack steckt.

Kann CaptchaAI hCaptcha in Puppeteer lösen?

Nein. hCaptcha und FunCaptcha (Arkose Labs) werden derzeit nicht unterstützt, und GeeTest v4 ist erst angekündigt. Für reCAPTCHA v2/v3, Cloudflare Turnstile und Challenge, GeeTest v3 sowie Bild- und Raster-CAPTCHAs funktioniert der gezeigte Ablauf dagegen unverändert.

Verwandte Leitfäden

Kommentare sind für diesen Artikel deaktiviert.