So lösen Sie Friendly Captcha in Node.js ohne Browser

solve friendly captcha in node.js - How to Solve Friendly Captcha in Node.js Without a Browser

Um Friendly Captcha in Node.js zu lösen, rufen Sie die Seite ab und behalten die Cookies, die sie setzt, lesen das Widget frc-captcha und sein Script-Tag mit cheerio aus, bestimmen anhand dieses Skripts v1 oder v2 und übergeben sitekey, Seiten-URL, Version und Skriptadresse an die Methode friendlyCaptcha von CapSkip. Senden Sie den zurückgegebenen Token zusammen mit den eigenen versteckten Feldern des Formulars ab, bei v2 in frc-captcha-response und bei v1 in frc-captcha-solution. Die meisten Fehlschläge haben hier zwei Ursachen. Das eingebaute fetch speichert keine Cookies, daher lehnt ein CSRF-geschütztes Formular selbst einen einwandfreien Token ab. Und wer die falsche Version löst, bekommt einen gültig aussehenden Token, der kommentarlos scheitert. CapSkip hat Friendly Captcha in 1.4.0 hinzugefügt, und dieser Leitfaden erledigt den ganzen Ablauf ohne Browser auf Ihrer Seite.

Was Sie brauchen

  • CapSkip 1.4.0 oder neuer auf einem Windows-Rechner. Mit diesem Release kam Friendly Captcha dazu, zusammen mit CaptchaFox und Capy Puzzle.
  • Node.js 22 oder neuer und Version 1.3.0 oder neuer des Pakets capskip, das erste Release mit friendlyCaptcha. Die Beispiele verwenden außerdem cheerio, um das HTML auszulesen.
  • Die URL der Seite, die das Widget anzeigt. Der sitekey, die Skriptadresse und der Feldname stammen alle aus dem HTML dieser Seite.
  • Eine Adresse für den Solver. Der Local-Modus antwortet auf 127.0.0.1 nur für dieses Gerät; der Server-Modus lauscht auf Ihrer Netzwerkadresse oder öffentlichen IP, damit ein Skript auf einem anderen Rechner ihn über die API aufrufen kann. Beide finden Sie unter Verbindungseinstellungen, und Schritt 4 erklärt, wann Sie wechseln sollten.
# npm install capskip cheerio
npm install capskip cheerio

Die Beispiele sind ES-Module mit Top-Level-await. Speichern Sie sie mit der Endung .mjs oder setzen Sie das Feld type in Ihrer package.json auf module (npm init trägt dort inzwischen commonjs ein), dann funktioniert import wie gezeigt. Das Paket capskip lässt sich auch mit require laden, wenn Ihr Projekt CommonJS ist.

Schritt 1: Die Seite abrufen und ihre Cookies behalten

Das fetch von Node ist ein guter HTTP-Client mit einer Lücke, die hier zählt: Es hat keinen Cookie-Speicher. Jeder Aufruf beginnt bei null, das Session-Cookie, das die Seite setzt, ist also weg, wenn Sie das Formular senden. Eine Website, die ihren CSRF-Token an diese Session bindet, lehnt das Absenden dann ab, oft mit einem 403, egal wie gut der Captcha-Token ist. In Python verdeckt requests.Session dieses Problem, in Node führen Sie die Cookies dagegen selbst mit.

Das Werkzeug dafür ist Headers.getSetCookie(), das jede Set-Cookie-Zeile einzeln zurückgibt. Ein einfacher Aufruf von headers.get kann das nicht leisten, denn er verbindet die Zeilen mit Kommas, und auch die Ablaufdaten von Cookies enthalten Kommas.

// npm install capskip cheerio
import * as cheerio from "cheerio";

const PAGE_URL = "https://example.com/signup";

// fetch() keeps no cookies between calls, so carry them by hand.
const jar = new Map();
function remember(res) {
  for (const line of res.headers.getSetCookie()) {
    const pair = line.split(";")[0];
    const eq = pair.indexOf("=");
    jar.set(pair.slice(0, eq).trim(), pair.slice(eq + 1).trim());
  }
}
const cookieHeader = () => [...jar].map(([k, v]) => `${k}=${v}`).join("; ");

const page = await fetch(PAGE_URL);
remember(page);
const $ = cheerio.load(await page.text());

Dieser Cookie-Speicher ist bewusst klein gehalten. Er behält Namen und Werte und ignoriert Pfade und Ablaufdaten, und mehr brauchen eine Website und ein Formular nicht.

Schritt 2: Das Widget auslesen, v1 oder v2 wählen und friendlyCaptcha aufrufen

Beide Versionen rendern dasselbe Element, ein div mit der Klasse frc-captcha und einem Attribut data-sitekey, das Widget allein verrät also nichts über das Protokoll. Das Script-Tag schon. Eine v2-Website lädt das Paket @friendlycaptcha/sdk mit der Datei site.min.js, eine v1-Website lädt friendly-challenge mit der Datei widget.module.min.js. Cheerio holt beides in wenigen Zeilen heraus:

const widget = $(".frc-captcha").first();
const form = widget.closest("form");

// Every script except nomodule fallbacks, as full addresses.
const scripts = $("script[src]").not("[nomodule]")
  .map((_, el) => new URL($(el).attr("src"), PAGE_URL).href)
  .get();

Die Seiten-URL als Basis zu übergeben ist bei selbst gehosteten Widgets wichtig. So wird aus einem relativen src wie /vendor/v2/site.min.js eine vollständige Adresse, und bei v2 lädt CapSkip genau dieses Skript in seinem eigenen Browser, um das Lösen auszuführen. Ein nackter Pfad lädt nie.

CapSkip wählt die Version in einer festen Reihenfolge und hört bei der ersten Antwort auf: zuerst die Version, die Sie übergeben, dann die Skriptadresse, die Sie als moduleScript übergeben, dann der Standardwert v1. Der Standardwert ist die Falle. Ein gebündeltes Skript wie /assets/app.4f2a.js verrät CapSkip nichts, also löst es v1, und eine v2-Website lehnt jeden Token ab. Entscheiden Sie stattdessen in Ihrem eigenen Code, wo Sie sich weigern können zu raten:

const V2_FILES = ["site.min.js", "site.compat.min.js"];
const V1_FILES = ["widget.module.min.js", "widget.min.js", "widget.polyfilled.min.js"];

function friendlyVersion(scripts, widget) {
  // Package names are unambiguous, so check them first.
  for (const src of scripts) {
    if (src.includes("@friendlycaptcha/sdk")) return { version: "v2", script: src };
    if (src.includes("friendly-challenge")) return { version: "v1", script: src };
  }
  // Self-hosted builds usually keep the file name. Themes ship their own
  // site.min.js too, so only trust a path that says friendly.
  for (const src of scripts.filter((s) => s.toLowerCase().includes("friendly"))) {
    const file = new URL(src).pathname.split("/").pop();
    if (V2_FILES.includes(file)) return { version: "v2", script: src };
    if (V1_FILES.includes(file)) return { version: "v1", script: src };
  }
  // Last resort: v2 and v1 name their widget options differently.
  if (widget.is("[data-api-endpoint], [data-form-field-name]")) return { version: "v2" };
  if (widget.is("[data-puzzle-endpoint], [data-solution-field-name]")) return { version: "v1" };
  throw new Error("v1 or v2? Read the page and set it by hand.");
}

Die Prüfung des Dateinamens vertraut nur Pfaden, die friendly enthalten, denn ein Theme kann ein eigenes site.min.js laden, das mit dem Widget nichts zu tun hat.

Dann folgt der Aufruf. Die Methode friendlyCaptcha nimmt den sitekey, die Seiten-URL und ein Options-Objekt entgegen. Das SDK verwirft undefined-Werte, bevor es etwas sendet, ein Attribut, das die Seite nicht hat, bleibt also einfach aus der Anfrage heraus:

import { CapSkip } from "capskip";

const solver = new CapSkip({ host: "127.0.0.1", port: 8080 });
const { version, script } = friendlyVersion(scripts, widget);

const result = await solver.friendlyCaptcha(widget.attr("data-sitekey"), PAGE_URL, {
  version,
  moduleScript: script,
  // EU sites: data-api-endpoint="eu" (v2) or data-puzzle-endpoint (v1)
  apiServer: widget.attr("data-api-endpoint") ?? widget.attr("data-puzzle-endpoint"),
});

console.log(result.token.slice(0, 40));   // v2 tokens start with AQQA.

Die Zeile mit apiServer ist für Websites am EU-Endpunkt von Friendly Captcha gedacht. Der globale Endpunkt stellt auch für einen EU-sitekey einen Token aus, ein Lösen dort scheitert also erst bei der Prüfung durch die Website selbst, dasselbe stille Scheitern wie bei der falschen Version. result.token ist die Zeichenfolge zum Absenden, result.code enthält dieselbe Zeichenfolge, und result.captchaId ist die ID von CapSkip für den Auftrag. Eine Version außer v1, v2, 1 oder 2, ein leerer sitekey oder eine Option, die die Methode nicht kennt, löst eine ValidationException aus, bevor überhaupt eine Anfrage Ihren Rechner verlässt.

Schritt 3: Den Token mit den eigenen Feldern des Formulars senden

Das Token-Feld steht nicht in dem HTML, das Sie abgerufen haben, denn das Widget-Skript erzeugt es erst im Browser. Sie fügen es also selbst hinzu, unter dem Namen, den Version und Widget verwenden. Alles andere, was das Formular mitbringt, einschließlich eines versteckten CSRF-Tokens, kommt aus dem Formular selbst:

// A renamed field wins; otherwise the default for the version.
const field = widget.attr("data-form-field-name")
  ?? widget.attr("data-solution-field-name")
  ?? (version === "v2" ? "frc-captcha-response" : "frc-captcha-solution");

// The form's own fields, hidden CSRF token included.
const body = new URLSearchParams(
  form.serializeArray().map((f) => [f.name, f.value]),
);
body.set("email", "YOUR_EMAIL");
body.set(field, result.token);

const res = await fetch(new URL(form.attr("action") || PAGE_URL, PAGE_URL), {
  method: "POST",
  // Browsers send the page as Referer; some servers refuse a post without it.
  headers: { cookie: cookieHeader(), referer: PAGE_URL },
  body,
});
console.log(res.status);

serializeArray sammelt die Felder, die ein Browser senden würde, und so gelangt der CSRF-Token zurück zum Server, ohne dass Sie ihn benennen müssen. Den Submit-Button lässt es weg, prüft die Website also den Namen des Buttons, fügen Sie ihn mit body.set hinzu. Senden Sie die Seiten-URL außerdem als Referer: fetch sendet weder einen Referer- noch einen Origin-Header, und manche Frameworks, darunter Django, lehnen einen HTTPS-Formular-POST ab, der keinen von beiden hat, egal wie korrekt Cookie und Token sind. Mit einem URLSearchParams-Body sendet fetch einen gewöhnlichen Formular-POST und setzt den Content-Type für Sie. Ein v2-Token ist etwa sechs Kilobyte groß, er gehört also in diesen Body und niemals in einen Query-String, während ein v1-Token aus vier durch Punkte getrennten Teilen und einigen hundert Zeichen besteht. Reichen Sie ihn unverändert weiter.

Zwei Fälle erfordern einen Blick in die DevTools. Ist bei v1 data-solution-field-name auf einen einzelnen Bindestrich gesetzt, schreibt das Widget überhaupt kein Feld, und das eigene Skript der Website sendet den Token auf anderem Weg. Manche Websites senden außerdem JSON per JavaScript, statt das Formular abzusenden. In beiden Fällen senden Sie einmal von Hand ab und übernehmen die Anfrage, die die Seite tatsächlich stellt.

Jeder Token gilt für genau ein Absenden. Die Verifizierung von Friendly Captcha lehnt eine Antwort ab, die bereits verwendet wurde oder abgelaufen ist, lösen Sie also für jedes Formular neu.

Schritt 4: Viele Formulare gleichzeitig und wo der Solver läuft

Jede CapSkip-Methode gibt in Node bereits ein Promise zurück, und AsyncCapSkip ist nur ein anderer Name für dieselbe Klasse, Nebenläufigkeit ist also ganz normales JavaScript. Was Sie brauchen, ist eine Obergrenze. Schicken Sie hundert Aufrufe durch Promise.all, reiht CapSkip alles, was über seine Einstellung Max. Threads für Friendly Captcha hinausgeht (standardmäßig 10), in eine Warteschlange ein, und dort wartet eine Aufgabe bis zu 250 Sekunden auf einen freien Thread, bevor CapSkip sie als gescheitert abbricht. Ein kleiner Worker-Pool hält höchstens zehn gleichzeitig in Arbeit:

// Run fn over items with at most `limit` in flight. A failed item
// becomes its Error instead of rejecting the whole batch.
async function mapLimited(items, limit, fn) {
  const results = new Array(items.length);
  let next = 0;
  async function worker() {
    while (next < items.length) {
      const i = next++;
      results[i] = await fn(items[i]).catch((err) => err);
    }
  }
  await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
  return results;
}

// jobs: { sitekey, url, version, script, apiServer } objects from Step 2
const tokens = await mapLimited(jobs, 10, (job) =>
  solver.friendlyCaptcha(job.sitekey, job.url, {
    version: job.version,
    moduleScript: job.script,
    apiServer: job.apiServer,
  }).then((r) => r.token));

Rechnen Sie mit schwankenden Lösezeiten. Friendly Captcha legt die Arbeit pro Anfrage fest und erhöht sie für Adressen, die es schon oft gesehen hat, und CapSkip löst jedes v2-Widget in einem echten Browser. Deshalb pollt die Methode bis zum recaptchaTimeout, standardmäßig 300 Sekunden, statt nur bis zum defaultTimeout von 120 Sekunden. CapSkip hat außerdem eine eigene Uhr: In seinen Friendly-Captcha-Einstellungen bekommt ein Lösevorgang 120 Sekunden Row Timeout, bevor er als gescheitert gilt. Mit timeout in den Optionen ändern Sie die Wartezeit des SDK für einen einzelnen Aufruf. Das SDK muss die Wartezeit auf einen Thread plus das Lösen überdauern, und mit den Standardwerten von CapSkip können das bis zu 370 Sekunden sein. Halten Sie den Pool also bei Max. Threads oder übergeben Sie ein längeres timeout, und erhöhen Sie es jedes Mal mit, wenn Sie Row Timeout erhöhen, sonst endet der Aufruf mit einer TimeoutException, während CapSkip noch arbeitet. Das Polling-Intervall lässt sich ebenfalls pro Aufruf setzen, aber nur als polling_interval in Snake Case. Das pollingInterval in camelCase gehört zum Konstruktor und löst als Option pro Aufruf eine ValidationException aus. Wenn das Lösen über einen langen Durchlauf hinweg langsamer wird, ist meist die steigende Schwierigkeit für eine Adresse die Ursache, übergeben Sie dann ein Proxy-Objekt mit den Schlüsseln type und uri oder richten Sie in CapSkip einen Proxy-Pool ein.

Die Beispiele verwenden 127.0.0.1, weil das richtig ist, wenn Node und der Solver auf demselben Rechner laufen. Läuft das Skript irgendwo anders, etwa auf einem VPS, in einem Container oder auf einem CI-Runner, zeigt Loopback auf genau diesen Rechner, und der erste Aufruf löst eine NetworkException mit ECONNREFUSED aus. Schalten Sie CapSkip in den Server-Modus, dann lauscht es auf Ihrer Netzwerkadresse oder öffentlichen IP, sodass jede dieser Umgebungen es über dieselbe API erreichen kann. Verwenden Sie eine statische öffentliche IP, wenn der Weg über das Internet führt, zusammen mit einer Firewall-Regel für die Adressen, die Sie erwarten. Es bleibt Ihre eigene Hardware, und es bleibt ohne Abrechnung pro Lösung. Der Client liest Umgebungsvariablen nicht von selbst, lesen Sie CAPSKIP_HOST also in Ihrem Code aus und übergeben Sie den Wert, wie es das vollständige Beispiel unten tut.

Vollständiges lauffähiges Beispiel

Hier ist der ganze Ablauf in einer Datei: der kürzeste vollständige Weg, Friendly Captcha in Node.js zu lösen, mit nichts als einer Seiten-URL als Ausgangspunkt.

// npm install capskip cheerio
// solve-friendly.mjs: run with node solve-friendly.mjs
import * as cheerio from "cheerio";
import { CapSkip, CapSkipError, ValidationException } from "capskip";

const PAGE_URL = "https://example.com/signup";
const V2_FILES = ["site.min.js", "site.compat.min.js"];
const V1_FILES = ["widget.module.min.js", "widget.min.js", "widget.polyfilled.min.js"];

const jar = new Map();
function remember(res) {
  for (const line of res.headers.getSetCookie()) {
    const pair = line.split(";")[0];
    const eq = pair.indexOf("=");
    jar.set(pair.slice(0, eq).trim(), pair.slice(eq + 1).trim());
  }
}
const cookieHeader = () => [...jar].map(([k, v]) => `${k}=${v}`).join("; ");

function friendlyVersion(scripts, widget) {
  for (const src of scripts) {
    if (src.includes("@friendlycaptcha/sdk")) return { version: "v2", script: src };
    if (src.includes("friendly-challenge")) return { version: "v1", script: src };
  }
  for (const src of scripts.filter((s) => s.toLowerCase().includes("friendly"))) {
    const file = new URL(src).pathname.split("/").pop();
    if (V2_FILES.includes(file)) return { version: "v2", script: src };
    if (V1_FILES.includes(file)) return { version: "v1", script: src };
  }
  if (widget.is("[data-api-endpoint], [data-form-field-name]")) return { version: "v2" };
  if (widget.is("[data-puzzle-endpoint], [data-solution-field-name]")) return { version: "v1" };
  throw new Error("v1 or v2? Read the page and set it by hand.");
}

const solver = new CapSkip({
  apiKey: process.env.CAPSKIP_API_KEY ?? "capskip",
  host: process.env.CAPSKIP_HOST ?? "127.0.0.1",
  port: Number(process.env.CAPSKIP_PORT ?? 8080),
});

const page = await fetch(PAGE_URL);
remember(page);
const $ = cheerio.load(await page.text());
const widget = $(".frc-captcha").first();
if (!widget.attr("data-sitekey")) {
  throw new Error("No frc-captcha widget in the HTML; it may be built by JS.");
}
const form = widget.closest("form");
const scripts = $("script[src]").not("[nomodule]")
  .map((_, el) => new URL($(el).attr("src"), PAGE_URL).href).get();

const { version, script } = friendlyVersion(scripts, widget);
let result;
try {
  result = await solver.friendlyCaptcha(widget.attr("data-sitekey"), PAGE_URL, {
    version,
    moduleScript: script,
    apiServer: widget.attr("data-api-endpoint") ?? widget.attr("data-puzzle-endpoint"),
  });
} catch (err) {
  if (err instanceof ValidationException) throw new Error(`not sent: ${err.message}`);
  if (err instanceof CapSkipError) throw new Error(`solve failed: ${err.name}: ${err.message}`);
  throw err;
}

const field = widget.attr("data-form-field-name")
  ?? widget.attr("data-solution-field-name")
  ?? (version === "v2" ? "frc-captcha-response" : "frc-captcha-solution");

// Post wherever the form's action points, with its other fields.
const body = new URLSearchParams(form.serializeArray().map((f) => [f.name, f.value]));
body.set("email", "YOUR_EMAIL");
body.set(field, result.token);

const res = await fetch(new URL(form.attr("action") || PAGE_URL, PAGE_URL), {
  method: "POST",
  // Browsers send the page as Referer; some servers refuse a post without it.
  headers: { cookie: cookieHeader(), referer: PAGE_URL },
  body,
});
console.log(res.status, version, field);

Die Version wird einmal bestimmt und zweimal verwendet, für das Lösen und für den Feldnamen, sodass beides nie auseinanderlaufen kann. Findet cheerio kein Widget, erzeugt die Seite es meist per JavaScript, und Sie brauchen den sitekey aus der gerenderten Seite oder aus dem Skript, das es erzeugt. Alle Parameter, die der rohe Endpunkt annimmt, finden Sie in der API-Referenz zu Friendly Captcha.

Häufige Fehler und was sie bedeuten

Was Sie sehenUrsacheBeheben
403 oder ein CSRF-Fehler, mit einem Token, den CapSkip zurückgegeben hatDas Absenden erfolgte ohne die Cookies der Seite, ihre versteckten Felder oder einen RefererSenden Sie den Cookie-Header aus Schritt 1 und einen Referer mit, und bauen Sie den Body aus serializeArray auf
Die Website lehnt einen Token ohne weitere Fehlermeldung abDie falsche Version wurde gelöst, oft v1 als StandardwertBestimmen Sie die Version wie in Schritt 2 im Code und übergeben Sie sie
Abgelehnt auf einer Website, deren Widget data-api-endpoint (v2) oder data-puzzle-endpoint (v1) hatDer Token kam vom globalen Endpunkt, und die Website nutzt den EU-EndpunktÜbergeben Sie den Wert des Attributs als apiServer
Ein v2-Lösevorgang scheitert auf einer Website, die das Widget selbst hostetmoduleScript war ein relativer Pfad, daher wurde das Widget nie geladenLösen Sie src mit new URL gegen die Seiten-URL auf
ValidationException, die pollingInterval nenntDie Option pro Aufruf heißt polling_intervalVerwenden Sie pro Aufruf Snake Case oder setzen Sie pollingInterval im Konstruktor
SyntaxError zu await oder importDie Datei läuft als CommonJSVerwenden Sie die Endung .mjs oder setzen Sie den Pakettyp auf module
TypeError: getSetCookie is not a functionNode.js älter als 18.15 bzw. 19.7Aktualisieren Sie auf Node.js 22 oder neuer
ApiException mit ERROR_CAPTCHA_UNSOLVABLE innerhalb von Sekunden, jedes MalFriendly Captcha lehnt den sitekey oder den Origin der Seite ab, oder im Konto ist weder v2 noch der EU-Endpunkt aktiviertPrüfen Sie sitekey, Seiten-URL und apiServer; ein erneuter Versuch hilft nicht
ApiException mit ERROR_CAPTCHA_UNSOLVABLE nach zwei Minuten oder mehrEin Lösevorgang lief länger als das Row Timeout von CapSkip mit 120 Sekunden oder wartete länger als das Wait Timeout von 250 Sekunden auf einen freien ThreadHalten Sie den Pool bei Max. Threads, fügen Sie Proxys hinzu oder erhöhen Sie Row Timeout in den Friendly-Captcha-Einstellungen
TimeoutException nach 300 SekundenEine Aufgabe wartete auf einen freien Thread, und der Lösevorgang lief danach länger, als das SDK polltHalten Sie den Pool bei Max. Threads oder übergeben Sie ein längeres timeout
NetworkException mit ECONNREFUSEDCapSkip läuft nicht, oder Host und Port sind falschStarten Sie CapSkip und prüfen Sie dann, ob der Local-Modus oder der Server-Modus passt

FAQ

Brauche ich Puppeteer oder Playwright, um Friendly Captcha in Node.js zu lösen?

Nein. Ihr Skript braucht nur das HTML, und das holt fetch. Die Browserarbeit findet in CapSkip statt, das v2-Widgets in seinem eigenen Browser und v1-Rätsel direkt löst. Wenn Ihr Skript aus anderen Gründen ohnehin einen Browser steuert, funktioniert derselbe Aufruf: Lesen Sie sitekey und Skriptadresse dann aus der Live-Seite statt aus dem abgerufenen HTML aus.

Funktioniert das in TypeScript?

Ja. Das Paket capskip bringt eigene Typdefinitionen mit, und das Options-Objekt ist sowohl mit den hier verwendeten camelCase-Namen als auch mit den snake_case-Namen der API typisiert. Ein Haken: Der gemeinsame Ergebnistyp deklariert token als optional und beschreibt es noch als ALTCHA-Feld, weil eine einzige Ergebnisform alle Methoden abdeckt. friendlyCaptcha füllt es immer, eine Non-Null-Assertion auf result.token ist also sicher. Auch attr von cheerio gibt string oder undefined zurück, sichern Sie sitekey und Skript-src im Strict Mode also auf dieselbe Weise per Assertion ab.

Kann eine Node-App auf einem VPS den Solver auf meinem Windows-PC nutzen?

Ja. Schalten Sie CapSkip in den Verbindungseinstellungen in den Server-Modus, damit es auf einer Netzwerkadresse statt auf Loopback lauscht, setzen Sie CAPSKIP_HOST dort, wo die App läuft, und übergeben Sie den Wert an den Client, wie es das vollständige Beispiel tut. Ein VPS, ein Container und ein gehosteter Runner verbinden sich alle über dieselbe HTTP-API. Verwenden Sie eine statische öffentliche IP mit einer Firewall-Regel, wenn der Weg über das Internet führt. Der Solver bleibt auf Hardware, die Ihnen gehört, mehr Lösevorgänge ändern also nie etwas an dem, was Sie zahlen.

Worin unterscheidet sich das vom Python-Leitfaden?

Endpunkt, Optionen und Ergebnis sind in jedem CapSkip SDK gleich; der Code drumherum ist es nicht. In Python verwaltet requests.Session die Cookies für Sie, während fetch in Node den kleinen Cookie-Speicher aus Schritt 1 braucht. AsyncCapSkip ist in Python ein eigener asynchroner Client, während in Node jede Methode ohnehin asynchron ist, Sie müssen also nur eine Grenze dafür ergänzen, wie viele gleichzeitig laufen. Die Python-Version dieses Workflows finden Sie im Python-Leitfaden zu Friendly Captcha.

Die Kurzfassung

Um Friendly Captcha in Node.js zu lösen, rufen Sie die Seite ab, behalten ihre Cookies mit getSetCookie und lesen dann das Element frc-captcha und seine Script-Tags mit cheerio aus, wobei Sie jedes src zu einer vollständigen Adresse auflösen. Bestimmen Sie v1 oder v2 anhand des Paket- oder Dateinamens oder der eigenen Attribute des Widgets, und werfen Sie eine Exception, wenn keines davon etwas verrät. Rufen Sie friendlyCaptcha mit dem sitekey, der Seiten-URL, dieser Version und der Skriptadresse auf, bei EU-Websites zusätzlich mit apiServer. Senden Sie den Token einmal ab, im Body, neben den eigenen Feldern des Formulars und in dem Feld, das die Version vorgibt.

Friendly Captcha verlangt für seine Schwierigkeit CPU-Zeit und kein Geld, ein arbeitsreicher Tag kostet Sie also Lösezeit auf Ihrem eigenen Rechner und sonst nichts. Wenn ein lokaler Captcha-Löser für Sie läuft, sind dieser Rechner und die Threads, die Sie ihm geben, die einzigen Grenzen.