Integrations

Utiliser Axios et CaptchaAI pour résoudre des CAPTCHA sans navigateur

Un CAPTCHA se résout très bien sans ouvrir le moindre navigateur. Si votre script Node.js se contente d'envoyer un formulaire ou d'appeler une API protégée, envoyez le défi à CaptchaAI, récupérez le token, puis injectez-le dans votre requête Axios : tout tient dans quelques appels HTTP.

Cette approche « sans navigateur » a un intérêt très concret côté serveur. Une instance headless mobilise couramment 200 à 500 Mo de RAM et plusieurs secondes de démarrage ; un client HTTP comme Axios reste à quelques mégaoctets. Sur un worker qui enchaîne des milliers de requêtes, cet écart pèse directement sur la stabilité, le temps de réponse et la facture d'hébergement.

Pourquoi se passer d'un navigateur

Le CAPTCHA n'est presque jamais la vraie raison de lancer un navigateur. Un navigateur devient utile quand la page dépend de JavaScript pour afficher son contenu ou calculer des champs cachés. La résolution du défi, elle, se déroule entièrement chez CaptchaAI : vous envoyez la clé du site et l'URL, le service renvoie un token valide. Aucun rendu local n'est nécessaire.

Ce modèle se prête bien à l'automatisation serveur, y compris sur le plan de la facturation. CaptchaAI facture au thread — un CAPTCHA en cours de traitement — et non à la résolution. L'offre BASIC ($15/mois, 5 threads) autorise déjà cinq résolutions simultanées, avec un nombre de résolutions illimité sur le mois. Vos appels Axios parallèles consomment donc des threads, pas un budget à l'unité, ce qui rend le coût prévisible même à fort volume.

Prérequis

Trois éléments suffisent pour démarrer.

Exigence Détails
Node.js 16+
axios 1.x
Clé API CaptchaAI créez un compte gratuit
npm install axios

Le module cheerio n'est requis que pour l'exemple de scraping complet plus bas ; ajoutez-le au moment voulu.

Un client CaptchaAI réutilisable en Node.js

La logique de résolution est toujours la même : on soumet le défi sur in.php, puis on interroge res.php jusqu'à obtenir le résultat. Encapsulez-la une fois dans une petite classe et réutilisez-la partout.

const axios = require("axios");

class CaptchaAI {
  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 });
    const text = resp.data;

    if (!String(text).startsWith("OK|")) {
      throw new Error(`Submit failed: ${text}`);
    }
    return String(text).split("|")[1];
  }

  async poll(taskId, timeoutMs = 300000) {
    const deadline = Date.now() + timeoutMs;
    const params = { key: this.apiKey, action: "get", id: taskId };

    while (Date.now() < deadline) {
      await new Promise((r) => setTimeout(r, 5000));

      const resp = await axios.get(`${this.baseUrl}/res.php`, { params });
      const text = String(resp.data);

      if (text === "CAPCHA_NOT_READY") continue;
      if (text.startsWith("OK|")) return text.split("|").slice(1).join("|");
      throw new Error(`Solve failed: ${text}`);
    }
    throw new Error(`Timeout after ${timeoutMs}ms for task ${taskId}`);
  }

  async solve(params, timeoutMs = 300000) {
    const taskId = await this.submit(params);
    return this.poll(taskId, timeoutMs);
  }

  async getBalance() {
    const resp = await axios.get(`${this.baseUrl}/res.php`, {
      params: { key: this.apiKey, action: "getbalance" },
    });
    return parseFloat(resp.data);
  }
}

module.exports = CaptchaAI;

Trois détails à retenir. La soumission réussit quand la réponse commence par OK|, sinon on lève une erreur avec le message brut. Le polling attend 5 secondes entre chaque appel et ignore le statut CAPCHA_NOT_READY. Enfin, solve() combine les deux étapes avec un délai d'expiration global de 5 minutes.

Résoudre reCAPTCHA v2 avec Axios

Pour reCAPTCHA v2, vous fournissez la googlekey (la clé publique du site) et l'pageurl. CaptchaAI renvoie le token à placer dans le champ g-recaptcha-response de la requête qui valide le formulaire.

const CaptchaAI = require("./captchaai");

async function main() {
  const solver = new CaptchaAI(process.env.CAPTCHAAI_API_KEY);

  // Solve the CAPTCHA without opening any browser
  const token = await solver.solve({
    method: "userrecaptcha",
    googlekey: "6Le-wvkS...",
    pageurl: "https://example.com/login",
  });

  // Submit form with the token using Axios
  const resp = await axios.post("https://example.com/login", {
    username: "user",
    password: "pass",
    "g-recaptcha-response": token,
  });

  console.log(`Login response: ${resp.status}`);
}

main().catch(console.error);

Le token a une durée de vie limitée : soumettez-le rapidement après la résolution, avant qu'il n'expire côté serveur.

Résoudre Cloudflare Turnstile

Turnstile suit exactement le même schéma. Seuls changent la method (turnstile), le paramètre sitekey et le champ de sortie, ici cf-turnstile-response.

const token = await solver.solve({
  method: "turnstile",
  sitekey: "0x4AAAAA...",
  pageurl: "https://example.com",
});

// Submit with Turnstile token
const resp = await axios.post("https://example.com/api/verify", {
  "cf-turnstile-response": token,
  data: "payload",
});

Résoudre un CAPTCHA image (OCR)

Pour un CAPTCHA image ou texte classique, pas de clé de site : vous envoyez l'image encodée en base64 avec la method base64, et le service renvoie directement le texte reconnu. Il n'y a pas de champ token à recopier, seulement la valeur à replacer dans votre formulaire.

const fs = require("fs");

const imageBuffer = fs.readFileSync("captcha.png");
const imageB64 = imageBuffer.toString("base64");

const text = await solver.solve({
  method: "base64",
  body: imageB64,
});

console.log(`CAPTCHA text: ${text}`);

// Submit form with solved text
const resp = await axios.post("https://example.com/verify", {
  captcha: text,
  other_data: "value",
});

Enchaîner un scraping HTTP de bout en bout

Voici le pipeline complet, sans navigateur : récupérer la page, en extraire la clé du défi avec cheerio, résoudre, puis renvoyer le formulaire avec le token. Un rappel de bon sens avant de vous lancer : ne collectez que des données que vous êtes autorisé à traiter, et gardez vos obligations RGPD en tête si des données personnelles transitent par votre pipeline.

const CaptchaAI = require("./captchaai");
const axios = require("axios");
const cheerio = require("cheerio");

async function scrapeProtectedPage(url) {
  const solver = new CaptchaAI(process.env.CAPTCHAAI_API_KEY);

  // Step 1: Fetch the page
  const page = await axios.get(url);
  const $ = cheerio.load(page.data);

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

  // Step 3: Solve the CAPTCHA
  console.log(`Solving CAPTCHA for ${url}...`);
  const token = await solver.solve({
    method: "userrecaptcha",
    googlekey: siteKey,
    pageurl: url,
  });

  // Step 4: Submit form with token
  const formAction = $("form").attr("action") || url;
  const formData = {};

  $("form input").each((_, el) => {
    const name = $(el).attr("name");
    const value = $(el).attr("value") || "";
    if (name) formData[name] = value;
  });
  formData["g-recaptcha-response"] = token;

  const result = await axios.post(formAction, new URLSearchParams(formData), {
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
  });

  return result.data;
}

scrapeProtectedPage("https://example.com/data")
  .then((data) => console.log("Success:", typeof data))
  .catch(console.error);

Le code repère d'abord la présence d'un data-sitekey : s'il n'y en a pas, la page n'est pas protégée et vous renvoyez son contenu tel quel. Sinon, vous recopiez tous les champs cachés du formulaire avant d'ajouter le token, sans quoi la soumission serait rejetée.

Résoudre plusieurs CAPTCHA en parallèle

Comme chaque résolution n'est qu'un appel HTTP, la parallélisation est triviale avec Promise.all. Une seule règle : calez votre niveau de concurrence sur le nombre de threads de votre plan.

async function solveBatch(urls, siteKey) {
  const solver = new CaptchaAI(process.env.CAPTCHAAI_API_KEY);

  const promises = urls.map(async (url) => {
    try {
      const token = await solver.solve({
        method: "userrecaptcha",
        googlekey: siteKey,
        pageurl: url,
      });
      return { url, token, error: null };
    } catch (error) {
      return { url, token: null, error: error.message };
    }
  });

  const results = await Promise.all(promises);

  const solved = results.filter((r) => r.token);
  console.log(`Solved ${solved.length}/${urls.length}`);
  return results;
}

Avec BASIC ($15/mois, 5 threads), lancez au plus cinq résolutions en vol simultanément ; au-delà, les requêtes attendent. Si votre débit augmente, l'offre STANDARD ($30/mois, 15 threads) triple la capacité sans changer une ligne de ce code — seul votre plafond de concurrence évolue.

Déployer côté serveur : VPS, OVHcloud, Scaleway ou serverless

C'est là que l'absence de navigateur paie vraiment. Un client HTTP tient dans une fonction serverless (AWS Lambda, Azure Functions, Google Cloud Functions) sans les contorsions habituelles d'un Chromium empaqueté, et démarre en quelques dizaines de millisecondes. Sur un petit VPS OVHcloud ou une instance Scaleway, vous faites tourner des dizaines de workers là où un seul navigateur headless saturerait la RAM.

Pour limiter la latence, hébergez le worker au plus près de vos sites cibles — une région européenne comme eu-west-3 (Paris) reste un choix naturel pour un public francophone. Gardez la clé API dans une variable d'environnement (CAPTCHAAI_API_KEY), jamais en dur dans le code, et surveillez le solde via getBalance() pour éviter une coupure en pleine campagne.

Dépannage

Erreur Cause probable Correctif
AxiosError: getaddrinfo ENOTFOUND Problème DNS ou réseau Vérifiez la connectivité et la résolution de nom du worker
Submit failed: ERROR_WRONG_USER_KEY Clé API incorrecte Contrôlez la clé dans le tableau de bord CaptchaAI
Submit failed: ERROR_ZERO_BALANCE Solde épuisé Rechargez le compte, puis vérifiez avec getBalance()
Boucle sur CAPCHA_NOT_READY puis timeout Défi encore en cours ou intervalle trop court Conservez l'intervalle de 5 s ; augmentez timeoutMs pour les types lents
Token refusé par le site cible Token expiré ou champs manquants Soumettez le token vite et recopiez tous les champs cachés du formulaire

FAQ

Combien de temps le token reste-t-il valide ?

Le token expire rapidement, généralement en une à deux minutes selon le type de CAPTCHA. Envoyez-le dans la foulée de la résolution : ne le stockez pas pour un usage différé.

Comment gérer la concurrence sans dépasser mon plan ?

Limitez le nombre de résolutions simultanées au nombre de threads de votre offre. Avec cinq threads, un lot de cinq appels Promise.all tourne à plein ; passez à un plan supérieur si vous avez besoin de plus de débit, le code reste identique.

Puis-je utiliser fetch ou got à la place d'Axios ?

Oui. Le fetch natif de Node.js 18+ ou une bibliothèque comme got fonctionnent tout aussi bien : l'API CaptchaAI attend les mêmes paramètres. Axios sert surtout d'illustration du pattern HTTP sans navigateur.

CaptchaAI prend-il en charge hCaptcha ?

Non — hCaptcha n'est pas pris en charge, et FunCaptcha (Arkose Labs) non plus. Le service résout en revanche reCAPTCHA v2 et v3, Cloudflare Turnstile et Challenge, GeeTest v3, ainsi que les CAPTCHA image/OCR et les grilles d'images.

Guides connexes

Les commentaires sont désactivés pour cet article.