Anwendungsfälle

CAPTCHA Scraping mit Node.js: Vollständiges Tutorial

Ihr Node.js-Scraper läuft stabil – bis eine Zielseite ein reCAPTCHA oder Cloudflare Turnstile ausspielt und die Antwort plötzlich eine Abfrage-Seite statt der erwarteten Daten enthält. Der direkte Weg daran vorbei: Sie lagern das CAPTCHA per API an CaptchaAI aus, setzen das zurückgegebene Token in das Formularfeld ein und führen den Request normal fort. Dieses Tutorial zeigt den vollständigen Ablauf mit Axios und Cheerio – vom Solver-Modul über das Scraping einer geschützten Seite bis zum parallelen Abarbeiten mehrerer URLs. Node.js eignet sich dafür besonders, weil es viele I/O-lastige Anfragen gleichzeitig verarbeitet, ohne dabei zu blockieren.

Voraussetzungen für das Setup

Für den Workflow brauchen Sie eine aktuelle Node.js-Laufzeit, zwei schlanke Bibliotheken und einen API-Schlüssel. Axios übernimmt die HTTP-Requests, Cheerio parst das zurückgegebene HTML serverseitig – ähnlich wie jQuery, aber ohne Browser.

Voraussetzung Details
Node.js 16+ Mit npm
Axios npm install axios
Cheerio npm install cheerio
CaptchaAI API-Schlüssel Aus captchaai.com

Das Solver-Modul für CaptchaAI

Kapseln Sie die Kommunikation mit CaptchaAI in einer eigenen Klasse. Das Modul kennt zwei interne Schritte: _submit reicht das CAPTCHA am Endpunkt in.php ein und erhält eine Task-ID zurück, _poll fragt danach am Endpunkt res.php im Fünf-Sekunden-Takt den Status ab, bis das Token vorliegt oder das Timeout greift. Nach außen stellt die Klasse je eine Methode für reCAPTCHA v2, reCAPTCHA v3 und Cloudflare Turnstile bereit.

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

class CaptchaSolver {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = "https://ocr.captchaai.com";
  }

  async _submit(params) {
    params.key = this.apiKey;
    const resp = await axios.get(`${this.baseUrl}/in.php`, { params });
    if (!resp.data.startsWith("OK|")) {
      throw new Error(`Submit error: ${resp.data}`);
    }
    return resp.data.split("|")[1];
  }

  async _poll(taskId, timeout = 300000) {
    const deadline = Date.now() + timeout;
    while (Date.now() < deadline) {
      await new Promise((r) => setTimeout(r, 5000));
      const resp = await axios.get(`${this.baseUrl}/res.php`, {
        params: { key: this.apiKey, action: "get", id: taskId },
      });
      if (resp.data === "CAPCHA_NOT_READY") continue;
      if (resp.data.startsWith("OK|")) return resp.data.split("|")[1];
      throw new Error(`Solve error: ${resp.data}`);
    }
    throw new Error("Solve timed out");
  }

  async solveRecaptchaV2(siteKey, pageUrl) {
    const taskId = await this._submit({
      method: "userrecaptcha",
      googlekey: siteKey,
      pageurl: pageUrl,
    });
    return this._poll(taskId);
  }

  async solveRecaptchaV3(siteKey, pageUrl, action = "verify") {
    const taskId = await this._submit({
      method: "userrecaptcha",
      googlekey: siteKey,
      pageurl: pageUrl,
      version: "v3",
      action,
    });
    return this._poll(taskId);
  }

  async solveTurnstile(siteKey, pageUrl) {
    const taskId = await this._submit({
      method: "turnstile",
      sitekey: siteKey,
      pageurl: pageUrl,
    });
    return this._poll(taskId);
  }
}

module.exports = CaptchaSolver;

Zur Abrechnung: CaptchaAI rechnet Thread-basiert ab – Sie zahlen pro gleichzeitig laufender Abfrage, nicht pro gelöstem CAPTCHA. Der Fünf-Sekunden-Abstand im Polling hält die Zahl offener Verbindungen niedrig und passt gut zu diesem Modell.

Eine reCAPTCHA-geschützte Seite scrapen

Der Ablauf folgt vier Schritten: Seite laden, den Sitekey aus dem .g-recaptcha-Element auslesen, das CAPTCHA lösen lassen und das Token gemeinsam mit den Formulardaten absenden. Findet Cheerio kein data-sitekey, ist die Seite ungeschützt und Sie verarbeiten das HTML direkt weiter.

const axios = require("axios");
const cheerio = require("cheerio");
const CaptchaSolver = require("./captcha-solver");

const solver = new CaptchaSolver("YOUR_API_KEY");

async function scrapeProtectedPage(url) {
  // Step 1: Load the page
  const { data: html } = await axios.get(url, {
    headers: {
      "User-Agent":
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    },
  });

  const $ = cheerio.load(html);

  // Step 2: Extract site key
  const siteKey = $(".g-recaptcha").attr("data-sitekey");
  if (!siteKey) {
    console.log("No CAPTCHA found, page loaded directly");
    return html;
  }

  console.log("Site key found:", siteKey);

  // Step 3: Solve the CAPTCHA
  const token = await solver.solveRecaptchaV2(siteKey, url);
  console.log("Token received:", token.substring(0, 50));

  // Step 4: Submit with the token
  const result = await axios.post(
    url,
    new URLSearchParams({
      "g-recaptcha-response": token,
      q: "search query",
    }),
    {
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        "User-Agent":
          "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
      },
    }
  );

  return result.data;
}

Mehrere Seiten parallel scrapen

Hier zahlt sich Node.js aus: Ein Worker-Pool arbeitet eine gemeinsame Warteschlange ab, sodass mehrere URLs gleichzeitig laufen. Die concurrency sollte zu Ihrem Plan passen – jede parallele Abfrage belegt einen Thread. Mit dem Tarif ADVANCE (90 $/Monat, 50 Threads) verarbeiten Sie deutlich mehr Seiten gleichzeitig als mit BASIC (15 $/Monat, 5 Threads).

async function scrapePages(urls, siteKey, concurrency = 3) {
  const results = [];
  const queue = [...urls];

  const worker = async () => {
    while (queue.length > 0) {
      const url = queue.shift();
      try {
        const token = await solver.solveRecaptchaV2(siteKey, url);
        const { data } = await axios.post(
          url,
          new URLSearchParams({ "g-recaptcha-response": token }),
          {
            headers: {
              "User-Agent":
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
            },
          }
        );
        results.push({ url, data, success: true });
        console.log(`Scraped: ${url}`);
      } catch (err) {
        results.push({ url, error: err.message, success: false });
        console.error(`Failed: ${url} - ${err.message}`);
      }
    }
  };

  // Run workers concurrently
  const workers = Array(concurrency)
    .fill(null)
    .map(() => worker());
  await Promise.all(workers);

  return results;
}

// Usage
const urls = [
  "https://example.com/page/1",
  "https://example.com/page/2",
  "https://example.com/page/3",
];
const results = await scrapePages(urls, "6Le-wvkS...", 3);

Cookies und Sitzungen verwalten

Manche Zielseiten setzen beim ersten Aufruf Sitzungscookies, die beim späteren POST wieder mitgeschickt werden müssen – andernfalls antwortet der Server mit 403 Forbidden. Kombinieren Sie Axios deshalb mit einem Cookie-Jar, der die Cookies über alle Requests hinweg hält:

const { wrapper } = require("axios-cookiejar-support");
const { CookieJar } = require("tough-cookie");

const jar = new CookieJar();
const client = wrapper(
  axios.create({
    jar,
    headers: {
      "User-Agent":
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    },
  })
);

async function scrapeWithSession(url, siteKey) {
  // Initial page load sets cookies
  await client.get(url);

  // Solve CAPTCHA
  const token = await solver.solveRecaptchaV2(siteKey, url);

  // Submit with maintained cookies
  const result = await client.post(
    url,
    new URLSearchParams({ "g-recaptcha-response": token })
  );

  return result.data;
}

Ergebnisse mit Cheerio parsen

Sobald der geschützte Endpunkt die eigentlichen Daten liefert, extrahiert Cheerio die gewünschten Felder über CSS-Selektoren. Das Muster ist bewusst schlicht gehalten und lässt sich an jede Zielstruktur anpassen:

function parseResults(html) {
  const $ = cheerio.load(html);
  const items = [];

  $(".result-item").each((_, el) => {
    items.push({
      title: $(el).find(".title").text().trim(),
      url: $(el).find("a").attr("href"),
      description: $(el).find(".description").text().trim(),
    });
  });

  return items;
}

Fehlerbehebung

Problem Ursache Lösung
CAPTCHA_NOT_READY läuft in eine Endlosschleife Falscher Sitekey oder langsame Lösung Sitekey prüfen; Timeout erhöhen
403 Forbidden beim POST Fehlende Cookies oder Header Sitzungscookies nutzen; Referer-Header ergänzen
Cheerio findet keine Elemente Inhalt wird per JavaScript nachgeladen Für JS-gerenderte Seiten Puppeteer einsetzen
ECONNREFUSED Zielseite drosselt die Anfragen (Rate-Limiting) Verzögerungen einbauen; Proxys rotieren

Rechtlicher Rahmen für Scraping im DACH-Raum

Bevor Sie einen Scraper produktiv einsetzen, lohnt ein Blick auf die Rechtslage. Öffentlich zugängliche Daten dürfen in der Regel erhoben werden, doch sobald personenbezogene Daten im Spiel sind, greift die DSGVO – und IP-Adressen gelten bereits als personenbezogen. Prüfen Sie daher Ihre Rechtsgrundlage, respektieren Sie die robots.txt und die AGB der Zielseite und begrenzen Sie die Abfragerate, um deren Infrastruktur nicht zu belasten. Für den Betrieb der Worker eignen sich europäische Anbieter wie Hetzner, IONOS oder netcup; wer bereits eine GitLab-CI-Pipeline nutzt, kann Scraping-Jobs dort geplant ausführen. CaptchaAI löst dabei ausschließlich die CAPTCHA-Abfrage – die Verantwortung für zulässiges Scraping bleibt bei Ihnen.

Häufige Fragen

Welche CAPTCHA-Typen löst CaptchaAI beim Scraping?

CaptchaAI löst reCAPTCHA v2 und v3, Cloudflare Turnstile und Cloudflare Challenge, GeeTest v3 sowie Bild-, OCR- und Raster-CAPTCHAs und BLS. Für klassisches Web-Scraping sind reCAPTCHA und Turnstile am relevantesten – genau die Methoden, die das Solver-Modul oben abdeckt. hCaptcha wird nicht unterstützt.

Was kostet CAPTCHA-Scraping mit CaptchaAI?

Die Abrechnung erfolgt Thread-basiert, nicht pro Lösung: Sie zahlen pro gleichzeitig laufender Abfrage und lösen innerhalb eines Threads unbegrenzt viele CAPTCHAs. Der Einstieg liegt bei 15 $/Monat (BASIC, 5 Threads), für parallele Scraping-Jobs bietet sich ADVANCE (90 $/Monat, 50 Threads) an. Preise in US-Dollar.

Warum bekomme ich nach dem Absenden des Tokens einen 403-Fehler?

Meist fehlen Sitzungscookies oder Header. Laden Sie die Seite zuerst mit einem Cookie-Jar, damit die beim ersten Aufruf gesetzten Cookies erhalten bleiben, und ergänzen Sie realistische User-Agent- und Referer-Header. Erst danach senden Sie das Token per POST ab.

Wie gehe ich mit Cloudflare-geschützten Seiten um?

Nutzt die Seite Turnstile, rufen Sie solver.solveTurnstile() auf und setzen das Token wie beim reCAPTCHA ein. Bei einer vollständigen Cloudflare-Challenge-Seite hilft die Lösung der Cloudflare Challenge, die cf_clearance-Cookies zurückgibt.

Ist Web-Scraping mit automatischer CAPTCHA-Lösung in Deutschland erlaubt?

Das hängt vom Einzelfall ab. Das Erheben öffentlich zugänglicher Daten ist grundsätzlich zulässig, doch AGB, Urheberrecht und die DSGVO setzen Grenzen – besonders bei personenbezogenen Daten. Klären Sie Ihre Rechtsgrundlage vorab und scrapen Sie nur Daten, für die Sie eine legitime Nutzung haben.

Verwandte Leitfäden

Kommentare sind für diesen Artikel deaktiviert.