实战教程

使用 Python 请求和 CaptchaAI 解决 Cloudflare Turnstile

Cloudflare Turnstile 正在取代数百万网站上的传统验证码。本指南向您展示如何使用 Python 的 requests 库和 CaptchaAI 的 API 解决 Turnstile 挑战 - 成功率达 100%。


先决条件

pip install requests

您需要:

  • 来自的 CaptchaAI API 密钥验证码网站
  • 目标页面 URL
  • Turnstile sitekey

第 1 步:提取 Turnstile 站点密钥

import re
import requests

def extract_turnstile_sitekey(url):
    """Extract Cloudflare Turnstile sitekey from page HTML."""
    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)

    patterns = [
        r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']',
        r"sitekey\s*:\s*['\"]([0-9x][A-Za-z0-9_-]+)['\"]",
        r"siteKey\s*[=:]\s*['\"]([0-9x][A-Za-z0-9_-]+)['\"]",
    ]

    for pattern in patterns:
        match = re.search(pattern, response.text)
        if match:
            return match.group(1)

    return None


sitekey = extract_turnstile_sitekey("https://example.com/signup")
print(f"Sitekey: {sitekey}")

第二步:提交至CaptchaAI

import requests

API_KEY = "YOUR_API_KEY"

def submit_turnstile(sitekey, page_url):
    """Submit Turnstile solving task to CaptchaAI."""
    response = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "turnstile",
        "sitekey": sitekey,
        "pageurl": page_url,
        "json": 1,
    })

    data = response.json()

    if data.get("status") != 1:
        raise Exception(f"Submit failed: {data.get('request')}")

    return data["request"]


task_id = submit_turnstile("0x4AAAAAAAC3DHQhMMQ_Rxrg", "https://example.com/signup")
print(f"Task ID: {task_id}")

第 3 步:投票结果

import time

def poll_result(task_id, timeout=120):
    """Poll CaptchaAI for the solved Turnstile token."""
    start = time.time()

    while time.time() - start < timeout:
        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"]

        if result.get("request") == "ERROR_CAPTCHA_UNSOLVABLE":
            raise Exception("Turnstile could not be solved")

    raise TimeoutError("Solve timed out")


token = poll_result(task_id)
print(f"Token: {token[:50]}...")

完整的工作示例

import re
import time
import requests

API_KEY = "YOUR_API_KEY"
TARGET_URL = "https://example.com/signup"


def solve_turnstile(sitekey, page_url):
    """Full Turnstile solve: submit + poll."""
    # Submit
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "turnstile",
        "sitekey": sitekey,
        "pageurl": page_url,
        "json": 1,
    })

    data = submit.json()
    if data.get("status") != 1:
        raise Exception(f"Submit error: {data.get('request')}")

    task_id = data["request"]
    print(f"Task submitted: {task_id}")

    # Poll
    for _ in range(30):
        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")


# --- Main flow ---
session = requests.Session()
session.headers.update({
    "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",
})

# 1. Get page and extract sitekey
response = session.get(TARGET_URL, timeout=15)
match = re.search(r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']', response.text)
if not match:
    raise ValueError("Turnstile sitekey not found")
sitekey = match.group(1)
print(f"Sitekey: {sitekey}")

# 2. Solve Turnstile
token = solve_turnstile(sitekey, TARGET_URL)
print(f"Token: {token[:50]}...")

# 3. Submit form with token
form_response = session.post(TARGET_URL, data={
    "cf-turnstile-response": token,
    "email": "user@example.com",
    "password": "SecurePass123",
})
print(f"Form status: {form_response.status_code}")

使用操作参数处理旋转门

一些站点在服务器端验证 action 参数:

def solve_turnstile_with_action(sitekey, page_url, action):
    """Solve Turnstile that requires an action parameter."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "turnstile",
        "sitekey": sitekey,
        "pageurl": page_url,
        "action": action,  # Include the action from data-action attribute
        "json": 1,
    })

    data = submit.json()
    if data.get("status") != 1:
        raise Exception(f"Submit error: {data.get('request')}")

    task_id = data["request"]

    for _ in range(30):
        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")

令牌提交模式

模式 1:使用 cf-turnstile-response 形成 POST

# Most common — Turnstile uses cf-turnstile-response field
response = session.post(form_url, data={
    "cf-turnstile-response": token,
    "email": "user@example.com",
})

模式 2:JSON API

response = session.post(api_url, json={
    "turnstileToken": token,
    "email": "user@example.com",
})

模式3:自定义字段名称

# Some sites rename the field — check the form HTML
response = session.post(form_url, data={
    "cf-turnstile-response": token,
    "captcha_token": token,  # Custom duplicate field
    "action": "signup",
})

生产就绪类

import re
import time
import requests

class TurnstileSolver:
    """Production-ready Turnstile solver with retry logic."""

    API_URL = "https://ocr.captchaai.com"

    def __init__(self, api_key, max_retries=3):
        self.api_key = api_key
        self.max_retries = max_retries

    def extract_sitekey(self, session, url):
        """Extract Turnstile sitekey from page."""
        response = session.get(url, timeout=15)
        match = re.search(
            r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']', response.text
        )
        return match.group(1) if match else None

    def solve(self, sitekey, page_url, action=None):
        """Solve Turnstile with retry logic. Returns token string."""
        for attempt in range(1, self.max_retries + 1):
            try:
                token = self._solve_once(sitekey, page_url, action)
                return token
            except TimeoutError:
                print(f"Attempt {attempt} timed out")
            except Exception as e:
                error_str = str(e)
                if "ERROR_ZERO_BALANCE" in error_str:
                    raise  # Don't retry billing errors
                if "ERROR_WRONG_USER_KEY" in error_str:
                    raise
                print(f"Attempt {attempt} failed: {e}")

        raise Exception(f"Failed after {self.max_retries} attempts")

    def _solve_once(self, sitekey, page_url, action=None):
        """Single solve attempt."""
        params = {
            "key": self.api_key,
            "method": "turnstile",
            "sitekey": sitekey,
            "pageurl": page_url,
            "json": 1,
        }
        if action:
            params["action"] = action

        submit = requests.post(f"{self.API_URL}/in.php", data=params, timeout=30)
        submit.raise_for_status()
        data = submit.json()

        if data.get("status") != 1:
            raise Exception(f"Submit error: {data.get('request')}")

        task_id = data["request"]

        for _ in range(30):
            time.sleep(5)
            result = requests.get(f"{self.API_URL}/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id,
                "json": 1,
            }, timeout=30).json()

            if result.get("status") == 1:
                return result["request"]
            if result.get("request") == "ERROR_CAPTCHA_UNSOLVABLE":
                raise Exception("CAPTCHA unsolvable")

        raise TimeoutError("Poll timed out")


# Usage
solver = TurnstileSolver("YOUR_API_KEY")
token = solver.solve("0x4AAAAAAAC3DHQhMMQ_Rxrg", "https://example.com/signup")

故障排除

症状 原因 处理方式
已收到令牌但表单被拒绝 站点密钥错误或缺少操作 重新提取站点密钥,包括操作(如果存在)
“未找到站点密钥” 通过 JavaScript 加载Turnstile 使用 Selenium 进行动态页面
获取页面之前的 HTTP 403 Cloudflare BIC 阻止请求 添加适当的浏览器标头
求解需要 >60 秒 队列拥堵 高峰时段正常,等待时间较长
令牌有效一次然后失败 站点每次尝试都需要新令牌 为每次提交解决一个新的 token

常见问题

成功率是多少?

CaptchaAI 在所有小部件模式下 Cloudflare Turnstile 的成功率达到 100%。

解决需要多长时间?

典型的求解时间为 5-15 秒。 Turnstile 通常比 reCAPTCHA 更快。

Turnstile 模式重要吗?

不会。托管、非交互和不可见模式都使用相同的 API 调用。 CaptchaAI 在内部处理模式差异。

如果页面同时具有 Turnstile 和 reCAPTCHA 该怎么办?

不常见但有可能。确定哪一个验证码保护您提交的表单并解决该问题。他们使用不同的响应字段名称。


概括

用 Python requests 解决 Cloudflare Turnstile 需要三个步骤:从页面 HTML 中提取 sitekey,提交到CaptchaAI使用 method=turnstile 并轮询令牌。在表单数据中提交作为 cf-turnstile-response 的令牌。 CaptchaAI 在所有 Turnstile 模式下都能提供 100% 的成功率。

相关文章

该文章已禁用评论。