Comparisons

Cloudflare 托管挑战与互动挑战

站点返回的是 503 还是 403?这一个状态码就够告诉你,现在面对的是托管挑战还是互动挑战。托管挑战由 Cloudflare 按访客风险自适应决定难度,互动挑战则不管风险高低一律显示可见验证码。分清楚两者,才能选对 CaptchaAI 的求解方法。


托管挑战:Cloudflare 怎么决定谁该被拦

托管挑战是 Cloudflare 推荐的默认动作,先跑一遍风险评估,再挑一个打扰最小的挑战级别:

判定逻辑

WAF rule triggers Managed Challenge
    ↓
Cloudflare evaluates visitor signals:
  ├─ Browser fingerprint quality
  ├─ IP reputation score
  ├─ TLS fingerprint (JA3/JA4)
  ├─ Request history
  ├─ Behavioral signals
  └─ Device capabilities
    ↓
Risk assessment → Challenge level selected:
  ├─ LOW risk → Invisible pass (no visible UI)
  ├─ MEDIUM risk → Non-interactive Turnstile (background PoW)
  ├─ HIGH risk → Interactive Turnstile (checkbox/widget)
  └─ VERY HIGH risk → JavaScript challenge page (5s wait)
    ↓
Challenge completed
    ↓
qa_session_cookie cookie issued

不同访客遇到的实际体验也不一样:

  • 常规浏览器,IP 信誉良好(约 90%)——什么都看不到,无感通过。
  • 新浏览器,IP 中性(约 5%)——短暂的加载动画。
  • 有可疑信号(约 4%)——弹出 Turnstile 复选框。
  • 高风险信号(约 1%)——整页显示"正在检查你的浏览器……"提示。

需要展示界面时,用的是 Cloudflare 自己的挑战平台:

<!-- Managed Challenge page (when visible) -->
<body>
    <div id="challenge-stage">
        <div id="challenge-body-text">
            Verifying you are human. This may take a few seconds.
        </div>

        <!-- Turnstile widget (when rendered) -->
        <div class="cf-turnstile"
             data-sitekey="0x4AAAAAAAC3DHQhMMQ_Rxrg">
        </div>
    </div>

    <!-- Challenge platform script -->
    <script src="/cdn-cgi/challenge-platform/h/g/orchestrate/managed/v1?ray=...">
    </script>
</body>

互动挑战:老规则,谁都躲不过

互动挑战是更早的旧规则,只要命中就一定展示必须手动完成的验证码——没有无感通过这回事。

请求流程

WAF rule triggers Interactive Challenge
    ↓
Full-page CAPTCHA served (HTTP 403)
    ↓
Visitor must interact with CAPTCHA widget
    ↓
CAPTCHA solved
    ↓
qa_session_cookie cookie issued

每个访客都会看到一个整页挑战,界面和 Turnstile 类似的小部件:

<!-- Interactive Challenge page -->
<body>
    <div id="challenge-running">
        <div class="main-wrapper">
            <h2>Please verify you are human</h2>

            <!-- Always-visible challenge widget -->
            <div class="challenge-widget">
                <!-- Checkbox + verification -->
            </div>
        </div>
    </div>
</body>

Cloudflare 不建议继续用它,原因很直接:

  • 始终可见——100% 的访客都会看到验证码。
  • 摩擦更高——每个访客都必须手动交互。
  • 跳出率更高——用户更容易直接离开,而不是完成验证。
  • 不做风险区分——就算是常客也照样被拦。
  • 可访问性差——每个人都得手动完成交互,无一例外。

一张表看懂:托管挑战 vs 互动挑战

一个容易被忽略的细节:这两种挑战都是 Cloudflare 自己的技术栈,不依赖 Google 的 reCAPTCHA 脚本,不会出现验证码图标在国内网络下转圈转不出来的情况——这也是不少跨境电商团队更愿意先排查 Cloudflare 挑战、再排查 reCAPTCHA 的原因。

对比项 托管挑战 互动挑战
上线时间 2021 年 更早的旧版规则(2021 年之前)
是否自适应 是,Cloudflare 按访客风险决定难度 否,一律要求互动
能不能无感通过 能,约 90% 的访客完全无感 不能,验证码始终可见
挑战形式 无感 → Turnstile → JS 挑战,逐级升级 验证码始终可见
Cloudflare 是否推荐 是,新规则默认用它 否,仅为兼容旧规则保留
用户摩擦 低,大多数人什么都看不到 高,每次都要手动交互
HTTP 状态码 503 403
CaptchaAI 方法 turnstilecloudflare_challenge turnstile

站点从互动迁移到托管,脚本要跟着改什么

Cloudflare 建议所有站点从互动挑战迁移到托管挑战。目标站点赶上这次迁移时,需要关注:

  1. 互动 → 托管:有约 90% 的概率开始无感通过。
  2. HTTP 403 → 503:状态码判断逻辑要跟着更新。
  3. 始终可见 → 自适应:小部件不一定会出现在 HTML 里。
  4. sitekey 不变:CaptchaAI 的求解代码不用改。
  5. qa_session_cookie 输出不变:cookie 处理逻辑不用改。

怎么判断当前面对的是哪种挑战

最可靠的办法是直接读 HTTP 状态码和响应体特征。下面这段脚本自动区分托管挑战、互动挑战、IUAM(旧版 JS 挑战)和纯 WAF 拦截四种情况:

import requests
import re

def identify_challenge_type(url):
    """Determine if a URL uses Managed or Interactive Challenge."""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 Chrome/120.0.0.0",
        "Accept": "text/html,*/*;q=0.8",
        "Accept-Language": "en-US,en;q=0.9",
    }

    response = requests.get(url, headers=headers, timeout=15, allow_redirects=False)
    html = response.text
    status = response.status_code

    result = {
        "url": url,
        "status": status,
        "challenge_type": None,
        "cf_ray": response.headers.get("cf-ray", ""),
        "solve_method": None,
    }

    if status == 200:
        # Check for inline Turnstile widget (not a challenge page)
        if "cf-turnstile" in html:
            result["challenge_type"] = "turnstile_widget"
            result["solve_method"] = "turnstile"
        else:
            result["challenge_type"] = "none"
        return result

    if status == 503:
        # 503 indicates Managed Challenge or IUAM
        if "managed" in html or "challenge-platform" in html:
            result["challenge_type"] = "managed_challenge"
            result["solve_method"] = "turnstile"  # Managed renders as Turnstile
        elif "jschl" in html:
            result["challenge_type"] = "iuam_js_challenge"
            result["solve_method"] = "cloudflare_challenge"
        else:
            result["challenge_type"] = "unknown_503"
        return result

    if status == 403:
        if "challenge" in html.lower():
            result["challenge_type"] = "interactive_challenge"
            result["solve_method"] = "turnstile"
        else:
            result["challenge_type"] = "waf_block"
            result["solve_method"] = None  # Hard block, not solvable
        return result

    return result


# Usage
info = identify_challenge_type("https://protected-site.com/login")
print(f"Challenge: {info['challenge_type']}")
print(f"Solve with: {info['solve_method']}")

判定特征表

特征 托管挑战 互动挑战 IUAM WAF 直接拦截
HTTP 状态码 503 403 503 403
HTML 中含 challenge-platform
脚本路径含 managed
HTML 中含 jschl
始终显示小部件
存在无感通过的可能

用 CaptchaAI 分别解决两种挑战

判断出挑战类型之后,接下来就是选对 method 参数——两种挑战最终都提交到同一组 in.php / res.php 接口,差别只在下面这一个字段。

解决托管挑战

需要交互时通常渲染成 Turnstile 小部件,用 CaptchaAI 的 turnstile 方法处理:

import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_managed_challenge(url, sitekey=None):
    """Solve Cloudflare Managed Challenge."""
    # If sitekey not provided, extract from page
    if not sitekey:
        import re
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                          "AppleWebKit/537.36 Chrome/120.0.0.0",
        }
        page = requests.get(url, headers=headers, timeout=15)
        match = re.search(
            r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']', page.text
        )
        sitekey = match.group(1) if match else None

    if not sitekey:
        # No visible Turnstile — try cloudflare_challenge method
        return solve_js_challenge(url)

    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "turnstile",
        "sitekey": sitekey,
        "pageurl": url,
        "json": 1,
    })

    task_id = submit.json()["request"]

    for _ in range(60):
        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.get("status") == 1:
            return result["request"]

    raise TimeoutError("Solve timed out")


def solve_js_challenge(url):
    """Fallback to cloudflare_challenge method."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "cloudflare_challenge",
        "sitekey": "managed",
        "pageurl": url,
        "json": 1,
    })

    task_id = submit.json()["request"]

    for _ in range(60):
        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.get("status") == 1:
            return result["request"]

    raise TimeoutError("Solve timed out")

解决互动挑战

互动挑战永远显示可见的小部件,用的是同一个 turnstile 方法:

def solve_interactive_challenge(url, sitekey):
    """Solve Cloudflare Interactive Challenge (legacy)."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "turnstile",
        "sitekey": sitekey,
        "pageurl": url,
        "json": 1,
    })

    task_id = submit.json()["request"]

    for _ in range(60):
        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.get("status") == 1:
            return result["request"]

    raise TimeoutError("Solve timed out")

Node.js(两种挑战通用)

Node.js 脚本可以用下面这段代码同时覆盖两种类型,靠 type 参数切换:

const axios = require("axios");

const API_KEY = "YOUR_API_KEY";

async function solveCloudflareChallenge(url, type = "managed") {
  const method = type === "js_challenge" ? "cloudflare_challenge" : "turnstile";
  const sitekey =
    type === "js_challenge" ? "managed" : await extractSitekey(url);

  const submit = await axios.post("https://ocr.captchaai.com/in.php", null, {
    params: {
      key: API_KEY,
      method,
      sitekey: sitekey || "managed",
      pageurl: url,
      json: 1,
    },
  });

  const taskId = submit.data.request;

  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));

    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: taskId, json: 1 },
    });

    if (result.data.status === 1) {
      return result.data.request;
    }
  }

  throw new Error("Solve timed out");
}

async function extractSitekey(url) {
  try {
    const response = await axios.get(url, {
      headers: { "User-Agent": "Mozilla/5.0 Chrome/120.0.0.0" },
      validateStatus: () => true,
    });
    const match = response.data.match(
      /data-sitekey=["']([0-9x][A-Za-z0-9_-]+)["']/
    );
    return match ? match[1] : null;
  } catch {
    return null;
  }
}

排查清单

  • 先看页面返回的是整页挑战,还是嵌在业务流程里的小部件,再确认有没有 cf-chl 标记、Turnstile 容器或对应回调钩子。
  • 把页面 URL 和代理出口信息一起记录下来,方便定位是不是同一个目标反复换了策略。

常见问题现象

症状 原因 处理方式
503,看不到任何小部件 托管挑战无感通过了 不用处理,你已经通过了
503,页面写着"正在检查你的浏览器" 托管挑战升级成了 JS 挑战 改用 cloudflare_challenge 方法
403,有可见验证码 命中的是互动挑战(旧版) turnstile 方法
403,但没有任何挑战内容 WAF 直接拦截,不是挑战 换 IP 或调整请求节奏
挑战类型时不时会变 托管挑战在按信号动态调整 Turnstile 和 JS 挑战都要覆盖到
一种类型下发的 cookie,另一种类型不认 两条挑战流程各自独立,域名相同 遇到哪种挑战就解哪种

常见问题

为什么同一个站点,这次是 503,下次却变成了 403?

大概率不是同一条 WAF 规则。一个站点可以对 /login 用托管挑战、对 /api/ 用互动挑战。看到状态码切换,先确认是不是命中了不同规则,而不是急着改代码。

只用 turnstile 方法,能不能同时搞定托管挑战和互动挑战?

大多数情况下可以,两者最常见的可见形式都是 Turnstile 小部件。只有当托管挑战升级成完整的 JS 挑战页面(503 加"正在检查你的浏览器")时,才需要切换到 cloudflare_challenge

互动挑战会被 Cloudflare 官方下线吗?

目前没有正式弃用公告,但 Cloudflare 已明确建议所有新规则改用托管挑战,互动挑战只为兼容旧配置保留,站点随时可能自行迁移。

托管挑战和互动挑战,能用同一个 CaptchaAI 账号处理吗?

可以。两者共用同一个 API Key,区别只在于 method 参数,计费也不区分挑战类型,按并发线程计算,一个线程解完就能接下一个任务。


小结

托管挑战按访客风险自适应选择难度,从完全无感到整页 JS 挑战;互动挑战一律显示可见验证码。两者都会产生同样的 qa_session_cookie cookie,用 CaptchaAI 处理时,大多数场景用 turnstile 方法即可,遇到完整 JS 挑战页面再切换到 cloudflare_challenge。托管挑战是现在的默认做法,互动挑战只是为兼容旧规则保留。

相关文章

该文章已禁用评论。