Tutorials

Extraction et résolution de clé de site Cloudflare Turnstile

Pour résoudre un défi Cloudflare Turnstile avec l'API CaptchaAI, deux valeurs suffisent : le sitekey du widget et l'URL de la page qui l'affiche. Le sitekey n'a rien de secret — il est intégré au HTML côté client pour que le navigateur charge le widget — mais Cloudflare l'expose de plusieurs façons selon la manière dont le site monte Turnstile. Ce guide parcourt les trois emplacements où le sitekey se cache, avec du code prêt à l'emploi pour chacun, puis montre comment envoyer la paire sitekey + URL à CaptchaAI et réinjecter le token obtenu.

Avant tout appel à l'API, vous cherchez donc exactement deux valeurs :

  • le sitekey du widget Turnstile (une chaîne qui commence par 0x4AAA...) ;
  • l'URL exacte de la page qui affiche le défi.

Où se trouve le sitekey Turnstile dans une page

Turnstile expose son sitekey à trois endroits, du plus simple au plus fiable :

  1. L'attribut data-sitekey sur les éléments .cf-turnstile
  2. L'appel JavaScript turnstile.render()
  3. L'URL src de l'iframe Turnstile

Commencez toujours par l'attribut DOM : c'est la méthode la moins coûteuse et elle fonctionne dès que la page est rendue côté serveur. Si le widget est monté par JavaScript, l'attribut peut manquer dans le HTML initial ; passez alors à l'analyse de l'appel turnstile.render(). Enfin, le src de l'iframe reste le repli le plus sûr : une fois le widget affiché, le sitekey y figure systématiquement.


Méthode 1 : lire l'attribut data-sitekey dans le DOM

Chaque élément .cf-turnstile porte son sitekey dans l'attribut data-sitekey. Le snippet suivant, à coller dans la console du navigateur, liste tous les widgets de la page ainsi que les attributs data-action et data-cdata — deux paramètres optionnels qu'il faudra transmettre au solveur s'ils sont présents.

// Browser console
document.querySelectorAll('.cf-turnstile').forEach((el, i) => {
  console.log(`Turnstile ${i}:`, {
    sitekey: el.getAttribute('data-sitekey'),
    action: el.getAttribute('data-action'),
    cData: el.getAttribute('data-cdata'),
    theme: el.getAttribute('data-theme'),
  });
});

Python (HTML statique)

Sur une page rendue côté serveur, une simple requête HTTP suffit : le sitekey est déjà dans le HTML renvoyé. Une expression régulière l'extrait sans piloter de navigateur.

import re
import requests

html = requests.get("https://example.com/login").text

matches = re.findall(
    r'class=["\'][^"\']*cf-turnstile[^"\']*["\'][^>]*data-sitekey=["\']([^"\']+)',
    html
)
for sk in matches:
    print(f"Sitekey: {sk}")

Python (Selenium)

Quand le widget n'apparaît qu'après exécution du JavaScript, chargez la page avec Selenium et lisez l'attribut une fois le DOM construit.

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://example.com/login")

widgets = driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile")
for w in widgets:
    sitekey = w.get_attribute("data-sitekey")
    action = w.get_attribute("data-action")
    print(f"Sitekey: {sitekey}, Action: {action}")

Méthode 2 : intercepter l'appel turnstile.render()

Certains sites ne posent aucun attribut data-sitekey dans le HTML : ils appellent turnstile.render() en passant le sitekey en paramètre. Il faut alors récupérer la valeur dans le code JavaScript.

turnstile.render('#captcha-container', {
  sitekey: '0x4AAAAAAAB...',
  callback: function(token) {
    document.getElementById('cf-token').value = token;
  },
});

Sur du HTML statique ou un bundle téléchargé, une regex sur l'appel turnstile.render isole la configuration :

# Find turnstile.render calls
render_match = re.search(
    r'turnstile\.render\s*\([^,]*,\s*\{([^}]+)\}',
    html
)
if render_match:
    config = render_match.group(1)
    sk = re.search(r'sitekey\s*:\s*["\']([^"\']+)', config)
    if sk:
        print(f"Sitekey from render: {sk.group(1)}")

Interception avec Puppeteer

Pour un site en single-page application, interceptez l'appel avant qu'il ne s'exécute. En redéfinissant window.turnstile avec Puppeteer, vous capturez les paramètres exacts passés au widget, sitekey compris :

// Intercept turnstile.render before page loads
await page.evaluateOnNewDocument(() => {
  window.__turnstileParams = [];
  const origRender = window.turnstile?.render;
  Object.defineProperty(window, 'turnstile', {
    set(val) {
      this._turnstile = val;
      const orig = val.render;
      val.render = function(container, params) {
        window.__turnstileParams.push(params);
        console.log('Turnstile render:', JSON.stringify(params));
        return orig.apply(this, arguments);
      };
    },
    get() { return this._turnstile; }
  });
});

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

const params = await page.evaluate(() => window.__turnstileParams);
console.log('Captured Turnstile params:', params);

Méthode 3 : récupérer le sitekey dans le src de l'iframe

Turnstile finit toujours par afficher une iframe pointant vers challenges.cloudflare.com, et le sitekey apparaît dans son src. C'est le repli le plus robuste : même si le sitekey a été injecté dynamiquement, il est présent dans l'URL de l'iframe une fois le widget monté.

Pour choisir rapidement la bonne méthode selon la page que vous ciblez :

  • HTML rendu côté serveur → méthode 1, l'attribut data-sitekey ;
  • sitekey posé par un script ou un bundle → méthode 2, l'appel turnstile.render() ;
  • widget déjà affiché mais sitekey introuvable dans le source → méthode 3, le src de l'iframe.
document.querySelectorAll('iframe').forEach(iframe => {
  if (iframe.src.includes('challenges.cloudflare.com')) {
    console.log('Turnstile iframe:', iframe.src);
    const match = iframe.src.match(/sitekey=([A-Za-z0-9_-]+)/);
    if (match) console.log('Sitekey:', match[1]);
  }
});

Résoudre Turnstile avec l'API CaptchaAI

Une fois le sitekey et l'URL en main, la résolution suit le schéma classique de l'API CaptchaAI : vous envoyez la tâche à in.php avec method=turnstile, puis vous interrogez res.php jusqu'à récupérer le token. Turnstile se résout généralement en moins de 10 secondes avec un taux de réussite élevé sur les types pris en charge.

Les chiffres de résolution reposent sur des mesures observées et des retours d'utilisateurs. Les résultats varient selon l'environnement, le volume et le moment de la journée.

Python

import requests
import time

API_KEY = "YOUR_API_KEY"
SITEKEY = "0x4AAAAAAAB..."
PAGE_URL = "https://example.com/login"

# Submit
resp = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "turnstile",
    "sitekey": SITEKEY,
    "pageurl": PAGE_URL,
    "json": "1",
}).json()

if resp["status"] != 1:
    raise Exception(f"Submit error: {resp['request']}")

task_id = resp["request"]

# Poll
for _ in range(24):
    time.sleep(5)
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY, "action": "get", "id": task_id, "json": "1"
    }).json()

    if result["status"] == 1:
        token = result["request"]
        print(f"Turnstile token: {token[:50]}...")
        break
    if result["request"] != "CAPCHA_NOT_READY":
        raise Exception(f"Error: {result['request']}")

JavaScript

const axios = require('axios');

const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
  params: {
    key: 'YOUR_API_KEY',
    method: 'turnstile',
    sitekey: '0x4AAAAAAAB...',
    pageurl: 'https://example.com/login',
    json: 1,
  }
});
const taskId = submit.data.request;

// Poll for result
let token = null;
for (let i = 0; i < 24; i++) {
  await new Promise(r => setTimeout(r, 5000));
  const poll = await axios.get('https://ocr.captchaai.com/res.php', {
    params: { key: 'YOUR_API_KEY', action: 'get', id: taskId, json: 1 }
  });
  if (poll.data.status === 1) {
    token = poll.data.request;
    break;
  }
}
console.log(`Token: ${token.substring(0, 50)}...`);

Côté facturation, CaptchaAI raisonne au thread simultané, sans coût par résolution : le forfait BASIC ($15/mois, 5 threads) couvre déjà un pipeline de surveillance modeste, et vous montez en threads quand la charge augmente. Pour un scraper qui contrôle, par exemple, un tunnel de paiement e-commerce en environnement de staging depuis un worker hébergé chez OVHcloud ou Scaleway, cinq threads absorbent sans peine les pics ponctuels. Si votre collecte touche des données personnelles, limitez-les au strict nécessaire et vérifiez vos obligations RGPD avant de journaliser quoi que ce soit.


Injecter le token cf-turnstile-response dans la page

Le token renvoyé par CaptchaAI n'est utile que s'il atteint le formulaire. Turnstile le stocke dans un champ caché nommé cf-turnstile-response : renseignez cette entrée, puis déclenchez le callback du widget pour que le site considère le défi comme validé.

# Selenium
driver.execute_script("""
    const input = document.querySelector('input[name="cf-turnstile-response"]');
    if (input) input.value = arguments[0];

    // Also set in the Turnstile widget's callback
    const widget = document.querySelector('.cf-turnstile');
    const callbackName = widget?.getAttribute('data-callback');
    if (callbackName && typeof window[callbackName] === 'function') {
        window[callbackName](arguments[0]);
    }
""", token)

Dépannage

Les échecs d'extraction ou de résolution se ramènent presque toujours à l'un de ces quatre cas :

Problème Cause Correctif
Aucun élément .cf-turnstile trouvé Widget rendu dynamiquement Attendez le chargement complet ou utilisez un MutationObserver
Sitekey vide Défini via l'API JavaScript Cherchez turnstile.render dans les scripts de la page
Token rejeté Sitekey ou URL de page erronés Revérifiez que les deux valeurs correspondent au site cible
Paramètre method incorrect userrecaptcha utilisé pour Turnstile Passez method=turnstile

FAQ

Le sitekey Turnstile est-il une donnée confidentielle ?

Non. Le sitekey est une clé publique intégrée au HTML côté client ; l'extraire n'a rien de sensible. La clé secrète, elle, reste sur le serveur du site et ne transite jamais par le navigateur.

En combien de temps CaptchaAI résout-il un défi Turnstile ?

Généralement en moins de 10 secondes, avec un taux de réussite élevé sur les types pris en charge. Un reCAPTCHA v2 peut demander sensiblement plus de temps. Ces durées sont des plafonds indicatifs, pas des moyennes garanties.

Faut-il transmettre data-action et data-cdata au solveur ?

Oui, lorsqu'ils sont présents sur le widget. Certaines intégrations Turnstile lient le token à ces paramètres ; les omettre fait rejeter le token même si le sitekey est correct.

Que faire si le widget Turnstile n'apparaît pas dans le HTML statique ?

Il est monté par JavaScript. Chargez la page avec un navigateur headless (Selenium ou Puppeteer), attendez le rendu, puis lisez l'attribut data-sitekey ou le src de l'iframe.


Résolvez les défis Cloudflare Turnstile avec CaptchaAI

Créez un compte et récupérez votre clé API sur captchaai.com pour envoyer votre première tâche method=turnstile.


Guides associés

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