Tutorials

Cloudflare Turnstile Sitekey 提取和解决

打开开发者工具却找不到 data-sitekey?别急着放弃——Turnstile 的 sitekey 只会藏在三个地方:DOM 属性、turnstile.render() 调用参数,或者 iframe 的 src 地址。本文按定位难度从低到高依次讲解,并给出用 CaptchaAI 提交 sitekey、换回 token 的完整代码。

跨境比价、海外票务监控这类项目遇到 Turnstile 的概率比纯国内项目高——国内站点更常见 GeeTest(极验),Turnstile 主要出现在面向海外用户的产品上,定位逻辑一样。


sitekey 藏在哪三个地方,先查哪个

Turnstile 的 sitekey 只会出现在这三处,不会有第四种。按实战优先级排序:

优先级 位置 查找成本 适用场景
1 .cf-turnstile 元素的 data-sitekey 属性 最低,无需执行 JS 静态渲染的页面
2 turnstile.render() 的 JavaScript 调用参数 中等,需解析脚本 运行时动态挂载
3 Turnstile iframe 的 src 地址 最高,但最稳 前两种查不到时兜底

按优先级从上到下试,iframe src 是最稳的兜底方案。


方法一:读取 DOM 属性

多数 Turnstile 集成会把 sitekey 直接写进 HTML,不需要执行任何 JavaScript 就能拿到。先在浏览器控制台跑一遍,确认页面上有几个 Turnstile 小部件,以及各自的参数:

// 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'),
  });
});

确认可行后,把同一个思路搬进采集脚本。静态页面和需要先跑 JS 的页面,处理方式不一样:

Python(静态页面)

如果目标页面的 HTML 源码里已经能看到 data-sitekey,直接请求页面、用正则提取即可,不用启动浏览器,速度最快:

import re
import requests

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

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

Python(Selenium)

遇到需要先执行 JavaScript 才会挂载 Turnstile 小部件的页面,改用 Selenium 加载完整 DOM 之后再读取属性:

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

driver = webdriver.Chrome()
driver.get("https://staging.example.com/qa-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}")

方法二:解析 JavaScript 渲染参数

DOM 里查不到 data-sitekey,说明站点运行时才调用 turnstile.render() 动态挂载小部件,sitekey 作为参数传进去:

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

得从页面源码或打包后的脚本里,把 turnstile.render 的调用参数用正则抠出来:

# 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)}")

Puppeteer 拦截渲染参数

脚本被压缩或混淆时正则不稳定,更可靠的办法是在页面加载前挂钩 turnstile.render,等站点调用时把参数截下来:

// 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://staging.example.com/qa-login', { waitUntil: 'networkidle2' });

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

方法三:解析 iframe src(兜底方案)

前两种方法都失败时不用慌——渲染完成后一定会生成一个 iframe,sitekey 就写在它的 src 查询参数里,是命中率最高的兜底方案:

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]);
  }
});

用 CaptchaAI 识别 Turnstile

拿到 sitekey 和页面 URL 之后,剩下的工作交给 CaptchaAI:提交任务、轮询结果、拿回 token。

只需要两个参数

sitekeypageurl,不需要额外配置。

Python

import requests
import time

API_KEY = "YOUR_API_KEY"
SITEKEY = "0x4AAAAAAAB..."
PAGE_URL = "https://staging.example.com/qa-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://staging.example.com/qa-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)}...`);

把 token 提交到目标页面

拿到 token 后写回页面的隐藏输入框,Turnstile 默认用 cf-turnstile-response 这个字段名保存令牌,同时触发一次小部件回调,兼容依赖回调取值的实现:

# 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)

常见报错与排查

现象 常见原因 处理方式
找不到 .cf-turnstile 元素 动态渲染,页面没加载完 等待加载或用 MutationObserver
读到的 sitekey 是空字符串 用 JavaScript API 设置,不在属性里 改用方法二搜索 turnstile.render
token 被目标站点拒绝 sitekey 或 pageurl 对不上 逐字核对两个参数
method 参数报错 误用了 userrecaptcha 固定用 method=turnstile
一直返回 CAPCHA_NOT_READY 任务还在处理中 按 5 秒轮询,最多 24 次(约 2 分钟)

常见问题

Turnstile 比 reCAPTCHA v2 更难识别吗?

不会,而且通常更快。CaptchaAI 处理两者的逻辑一致,只是 method 参数不同:Turnstile 一般 <10 秒出结果,reCAPTCHA v2 是 <60 秒。

页面上没有 .cf-turnstile 元素,怎么确认站点用了 Turnstile?

看是否请求了 challenges.cloudflare.com 的脚本,或者网络面板里出现指向该域名的 iframe。任一成立就说明用了 Turnstile,按方法二、三继续定位 sitekey。

managed 和 non-interactive 模式下会不会没有 sitekey?

不会。这两种模式只是不显示可见的小部件,data-sitekey、渲染参数和 iframe src 照常存在,三种方法都通用。

sitekey 会不会随页面更新而变化?

有可能,尤其站点调整安全策略后。不建议硬编码,每次采集任务重新抓取更稳妥。


立即用 CaptchaAI 识别 Turnstile

不用自己维护识别逻辑——前往 CaptchaAI 官网 注册账号、领取 API Key,直接跑通上面的示例代码。


相关指南

该文章已禁用评论。