有些网站在登录页面上使用 reCAPTCHA,在结帐页面上使用 Turnstile。其他 A/B 提供商之间的测试 - 相同的 URL 显示一个访客的 reCAPTCHA 和另一个访客的 Turnstile。对单个求解器方法进行硬编码意味着只要遇到其他类型,您的自动化就会中断。
为什么网站使用多个验证码提供程序
| 设想 | 它看起来如何 |
|---|---|
| 不同的页面,不同的提供商 | 登录 = reCAPTCHA,结帐 = Turnstile |
| A/B 测试提供商 | 同一页面随机显示任一类型 |
| 迁移正在进行中 | 旧页面有 reCAPTCHA,新页面有 Turnstile |
| 失败时的回退 | 主要提供商失败 → 回退到辅助提供商 |
| 地区差异 | 针对美国访客的 reCAPTCHA、针对欧盟访客的 Turnstile (GDPR) |
Python:自动检测和解决
import requests
import time
import re
from dataclasses import dataclass
API_KEY = "YOUR_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
@dataclass
class CaptchaInfo:
provider: str # "recaptcha" or "turnstile"
method: str # API method name
sitekey: str
pageurl: str
response_field: str # Form field name for the token
def detect_captcha_type(html, pageurl):
"""
Detect which CAPTCHA provider is on the page.
Returns CaptchaInfo or None.
"""
# Check for Turnstile
turnstile_match = re.search(
r'class=["\'][^"\']*cf-turnstile[^"\']*["\'][^>]*data-sitekey=["\']([^"\']+)["\']',
html,
)
if not turnstile_match:
turnstile_match = re.search(
r'data-sitekey=["\']([^"\']+)["\'][^>]*class=["\'][^"\']*cf-turnstile',
html,
)
if turnstile_match:
return CaptchaInfo(
provider="turnstile",
method="turnstile",
sitekey=turnstile_match.group(1),
pageurl=pageurl,
response_field="cf-turnstile-response",
)
# Check for reCAPTCHA
recaptcha_match = re.search(
r'class=["\'][^"\']*g-recaptcha[^"\']*["\'][^>]*data-sitekey=["\']([^"\']+)["\']',
html,
)
if not recaptcha_match:
recaptcha_match = re.search(
r'data-sitekey=["\']([^"\']+)["\'][^>]*class=["\'][^"\']*g-recaptcha',
html,
)
# Also check for script-rendered reCAPTCHA
if not recaptcha_match:
recaptcha_match = re.search(
r'grecaptcha\.render\([^,]+,\s*\{[^}]*["\']sitekey["\']\s*:\s*["\']([^"\']+)["\']',
html,
)
if recaptcha_match:
return CaptchaInfo(
provider="recaptcha",
method="userrecaptcha",
sitekey=recaptcha_match.group(1),
pageurl=pageurl,
response_field="g-recaptcha-response",
)
return None
def solve_captcha(info):
"""Solve any detected CAPTCHA type via CaptchaAI."""
params = {
"key": API_KEY,
"method": info.method,
"json": 1,
}
if info.method == "userrecaptcha":
params["googlekey"] = info.sitekey
params["pageurl"] = info.pageurl
elif info.method == "turnstile":
params["sitekey"] = info.sitekey
params["pageurl"] = info.pageurl
resp = requests.post(SUBMIT_URL, data=params, timeout=30).json()
if resp.get("status") != 1:
raise RuntimeError(f"Submit failed: {resp.get('request')}")
task_id = resp["request"]
for _ in range(60):
time.sleep(5)
poll = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "get",
"id": task_id, "json": 1,
}, timeout=15).json()
if poll.get("request") == "CAPCHA_NOT_READY":
continue
if poll.get("status") == 1:
return poll["request"]
raise RuntimeError(f"Solve failed: {poll.get('request')}")
raise RuntimeError("Timeout")
def process_page(session, url):
"""Fetch page, detect CAPTCHA type, solve, and return form-ready data."""
response = session.get(url)
captcha_info = detect_captcha_type(response.text, url)
if not captcha_info:
print(f"No CAPTCHA detected on {url}")
return None
print(f"Detected {captcha_info.provider} on {url}")
print(f" Sitekey: {captcha_info.sitekey[:30]}...")
token = solve_captcha(captcha_info)
print(f" Solved: {token[:30]}...")
return {
"provider": captcha_info.provider,
"response_field": captcha_info.response_field,
"token": token,
}
# Usage: Handle multiple pages with different providers
session = requests.Session()
pages = [
"https://staging.example.com/qa-login", # Might have reCAPTCHA
"https://example.com/checkout", # Might have Turnstile
]
for url in pages:
result = process_page(session, url)
if result:
form_data = {result["response_field"]: result["token"]}
# Add other form fields...
# session.post(url, data=form_data)
JavaScript:动态验证码检测
const API_KEY = "YOUR_API_KEY";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
function detectCaptchaType(html, pageurl) {
// Turnstile
const turnstileMatch = html.match(/cf-turnstile[^>]*data-sitekey=["']([^"']+)["']/);
if (turnstileMatch) {
return { provider: "turnstile", method: "turnstile", sitekey: turnstileMatch[1], pageurl, field: "cf-turnstile-response" };
}
// reCAPTCHA
const recaptchaMatch = html.match(/g-recaptcha[^>]*data-sitekey=["']([^"']+)["']/);
if (recaptchaMatch) {
return { provider: "recaptcha", method: "userrecaptcha", sitekey: recaptchaMatch[1], pageurl, field: "g-recaptcha-response" };
}
// Script-rendered reCAPTCHA
const scriptMatch = html.match(/sitekey["']\s*:\s*["']([^"']+)["']/);
if (scriptMatch) {
return { provider: "recaptcha", method: "userrecaptcha", sitekey: scriptMatch[1], pageurl, field: "g-recaptcha-response" };
}
return null;
}
async function solveCaptcha(info) {
const body = new URLSearchParams({ key: API_KEY, method: info.method, json: "1" });
if (info.method === "userrecaptcha") { body.set("googlekey", info.sitekey); body.set("pageurl", info.pageurl); }
else if (info.method === "turnstile") { body.set("sitekey", info.sitekey); body.set("pageurl", info.pageurl); }
const resp = await (await fetch(SUBMIT_URL, { method: "POST", body })).json();
if (resp.status !== 1) throw new Error(`Submit: ${resp.request}`);
const taskId = resp.request;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const url = `${RESULT_URL}?key=${API_KEY}&action=get&id=${taskId}&json=1`;
const poll = await (await fetch(url)).json();
if (poll.request === "CAPCHA_NOT_READY") continue;
if (poll.status === 1) return poll.request;
throw new Error(`Solve: ${poll.request}`);
}
throw new Error("Timeout");
}
async function processPage(url) {
const response = await fetch(url);
const html = await response.text();
const info = detectCaptchaType(html, url);
if (!info) { console.log(`No CAPTCHA on ${url}`); return null; }
console.log(`${info.provider} detected on ${url}`);
const token = await solveCaptcha(info);
return { provider: info.provider, field: info.field, token };
}
// Usage
const pages = ["https://staging.example.com/qa-login", "https://example.com/checkout"];
for (const url of pages) {
const result = await processPage(url);
if (result) {
console.log(`Solved ${result.provider}: ${result.token.substring(0, 30)}...`);
}
}
提供商检测参考
| 提供商 | HTML 标记 | 脚本网址 | 响应字段 |
|---|---|---|---|
| reCAPTCHA v2 | class="g-recaptcha" |
google.com/recaptcha/api.js |
g-recaptcha-response |
| Cloudflare Turnstile | class="cf-turnstile" |
challenges.cloudflare.com/turnstile |
cf-turnstile-response |
| 验证码 | class="h-captcha" |
js.hcaptcha.com/1/api.js |
h-captcha-response |
故障排除
| 问题 | 原因 | 处理方式 |
|---|---|---|
| 检测到错误的验证码类型 | 正则表达式匹配错误的元素 | 首先检查提供者特定的类名,而不仅仅是 data-sitekey |
| 正确解决后令牌被拒绝 | 使用了错误的响应字段名称 | 将字段名称与提供者匹配:g-recaptcha-response 与 cf-turnstile-response |
| 验证码类型在访问之间发生变化 | A/B 测试或基于地理的选择 | 始终动态检测;永远不要对提供者进行硬编码 |
| 在一页上检测到两个提供商 | 一个可能被隐藏/inactive | 检查元素可见性 - 仅解决可见的验证码 |
| 脚本呈现的验证码检测失败 | 源代码中没有 HTML 标记 | 检查脚本中的 grecaptcha.render() 或 turnstile.render() 调用 |
常问问题
页面可以同时使用 reCAPTCHA 和 Turnstile 吗?
很少采用相同的形式,但它发生在网站的不同部分。如果两者都出现在一页上,通常只有一个处于活动状态 - 检查哪个小部件可见并且具有非空 data-sitekey。
CaptchaAI 是否为两个提供商使用相同的 API?
相同的端点 (in.php / res.php) 但 method 值不同。 reCAPTCHA 使用 method=userrecaptcha 和 googlekey,而 Turnstile 使用 method=turnstile 和 sitekey。自动检测模式处理此映射。
我如何处理提供商故障转移?
如果站点在会话中从 reCAPTCHA 切换到 Turnstile(例如,在 reCAPTCHA 加载失败后),请重新检测每个页面请求上的 CAPTCHA 类型,而不是缓存第一次访问时的提供程序。
相关文章
下一步
处理具有多个验证码提供商的网站 —获取您的 CaptchaAI API 密钥并实现自动检测。
相关指南: