同一页面出现两个甚至更多验证码时,正确做法是并行检测、并行提交,按 sitekey 回填 token——串行也能跑通,但耗时会翻倍。常见场景:登录 + 订阅复合表单,或海外电商分步注册(第一步 reCAPTCHA,第二步 Turnstile)。
检测与并行提交
先检测:页面上到底有几个验证码
用 CSS 选择器抓出所有 reCAPTCHA iframe,再从 src 里用正则提取 sitekey,不要假设页面只有一个。
Python(Selenium)
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://example.com/multi-captcha-page")
# Find all reCAPTCHA iframes
captcha_iframes = driver.find_elements(
By.CSS_SELECTOR, 'iframe[src*="recaptcha/api2/anchor"]'
)
# Extract sitekeys from each iframe's src
import re
captchas = []
for i, iframe in enumerate(captcha_iframes):
src = iframe.get_attribute("src")
match = re.search(r"k=([A-Za-z0-9_-]+)", src)
if match:
captchas.append({
"index": i,
"sitekey": match.group(1),
"iframe": iframe
})
print(f"Found {len(captchas)} CAPTCHAs on page")
for c in captchas:
print(f" [{c['index']}] sitekey: {c['sitekey']}")
预期输出:
Found 2 CAPTCHAs on page
[0] sitekey: 6LcXyzABCDEF-login
[1] sitekey: 6LcAbcDEFGHI-signup
JavaScript(Puppeteer)
Puppeteer 直接在页面上下文查询 .g-recaptcha widget,一次拿到 sitekey、DOM id 和 callback 名称。
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://example.com/multi-captcha-page', {
waitUntil: 'networkidle2'
});
// Extract all reCAPTCHA widgets from the page
const captchas = await page.evaluate(() => {
const widgets = document.querySelectorAll('.g-recaptcha');
return Array.from(widgets).map((el, i) => ({
index: i,
sitekey: el.getAttribute('data-sitekey'),
elementId: el.id || `captcha-${i}`,
callbackName: el.getAttribute('data-callback') || null
}));
});
console.log(`Found ${captchas.length} CAPTCHAs`);
captchas.forEach(c => console.log(` [${c.index}] ${c.sitekey}`));
})();
并行提交,同时轮询结果
每个验证码独立提交给 CaptchaAI,用线程池同时轮询——单个解决约 15-30 秒,并行处理两个和一个耗时基本相同。
Python
import requests
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
API_KEY = "YOUR_API_KEY"
PAGE_URL = "https://example.com/multi-captcha-page"
def submit_captcha(sitekey):
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": PAGE_URL,
"json": "1"
})
result = resp.json()
if result["status"] != 1:
raise Exception(f"Submit error: {result['request']}")
return result["request"]
def poll_result(task_id, timeout=120):
deadline = time.time() + timeout
while time.time() < deadline:
time.sleep(5)
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY,
"action": "get",
"id": task_id,
"json": "1"
})
result = resp.json()
if result["status"] == 1:
return result["request"]
if result["request"] != "CAPCHA_NOT_READY":
raise Exception(f"Poll error: {result['request']}")
raise TimeoutError(f"Task {task_id} timed out")
def solve_all(captchas):
# Submit all in parallel
task_ids = {}
with ThreadPoolExecutor(max_workers=len(captchas)) as pool:
futures = {
pool.submit(submit_captcha, c["sitekey"]): c["index"]
for c in captchas
}
for future in as_completed(futures):
idx = futures[future]
task_ids[idx] = future.result()
print(f"[captcha-{idx}] Submitted → task {task_ids[idx]}")
# Poll all in parallel
tokens = {}
with ThreadPoolExecutor(max_workers=len(task_ids)) as pool:
futures = {
pool.submit(poll_result, tid): idx
for idx, tid in task_ids.items()
}
for future in as_completed(futures):
idx = futures[future]
tokens[idx] = future.result()
print(f"[captcha-{idx}] Solved")
return tokens
# Example usage
captchas = [
{"index": 0, "sitekey": "6LcXyzABCDEF-login"},
{"index": 1, "sitekey": "6LcAbcDEFGHI-signup"}
]
tokens = solve_all(captchas)
预期输出:
[captcha-0] Submitted → task 71823456
[captcha-1] Submitted → task 71823457
[captcha-1] Solved
[captcha-0] Solved
把 token 精确回填到验证码控件
每个 reCAPTCHA 小部件都有独立的 g-recaptcha-response 文本域——按 sitekey 回填,不要按出现顺序猜测,异步加载时顺序不可靠。
Python(Selenium)
def inject_tokens(driver, captchas, tokens):
for c in captchas:
idx = c["index"]
token = tokens[idx]
# Find the textarea within the widget's container
container = driver.find_elements(By.CSS_SELECTOR, ".g-recaptcha")[idx]
textarea = container.find_element(
By.CSS_SELECTOR, 'textarea[name="g-recaptcha-response"]'
)
driver.execute_script(
"arguments[0].value = arguments[1];", textarea, token
)
# Trigger the callback if defined
callback = c.get("callback")
if callback:
driver.execute_script(f"{callback}('{token}');")
print(f"[captcha-{idx}] Token injected")
inject_tokens(driver, captchas, tokens)
JavaScript(Puppeteer)
async function injectTokens(page, captchas, tokens) {
for (const captcha of captchas) {
const token = tokens[captcha.index];
await page.evaluate((idx, tkn, callbackName) => {
const widgets = document.querySelectorAll('.g-recaptcha');
const textarea = widgets[idx].querySelector(
'textarea[name="g-recaptcha-response"]'
);
textarea.value = tkn;
if (callbackName && typeof window[callbackName] === 'function') {
window[callbackName](tkn);
}
}, captcha.index, token, captcha.callbackName);
console.log(`[captcha-${captcha.index}] Token injected`);
}
}
await injectTokens(page, captchas, tokens);
reCAPTCHA 与 Turnstile 混合出现时怎么处理
有些页面前后两步验证码类型不同——比如海外电商结账页,第一步 reCAPTCHA,第二步换成 Turnstile。分别检测每种类型,匹配正确的 method 参数:
def detect_all_captchas(driver):
detected = []
# reCAPTCHA
recaptchas = driver.find_elements(By.CSS_SELECTOR, ".g-recaptcha")
for i, el in enumerate(recaptchas):
detected.append({
"type": "userrecaptcha",
"sitekey": el.get_attribute("data-sitekey"),
"label": f"recaptcha-{i}"
})
# Turnstile
turnstiles = driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile")
for i, el in enumerate(turnstiles):
detected.append({
"type": "turnstile",
"sitekey": el.get_attribute("data-sitekey"),
"label": f"turnstile-{i}"
})
return detected
- reCAPTCHA 提交时
method用userrecaptcha - Turnstile 提交时
method用turnstile,两种类型不要混用同一个 method
常见问题与故障排查
遇到报错先查这张表,多数问题都能在这里定位:
| 问题 | 原因 | 处理 |
|---|---|---|
| token 已回填,表单还提交不了 | 未触发 callback | 检查 data-callback |
| 只检测到第一个验证码 | 第二个延迟加载 | 等 widget 出现后再扫描 |
| token 塞进错误控件 | 按位置索引而非 sitekey | 用 sitekey 做映射键 |
ERROR_WRONG_GOOGLEKEY |
sitekey 提取错了 | 核对 src 或 data-sitekey |
页面出现多个验证码,是不是出问题了?
不是,这是常见设计——登录 + 订阅复合表单、分步注册流程都会让每一步触发一次验证,按独立任务处理即可。
reCAPTCHA 和 Turnstile 混合出现时,提交参数要分开传吗?
要分开。reCAPTCHA 用 userrecaptcha,Turnstile 用 turnstile,混用会导致其中一个提交失败。
同时处理多个验证码要按数量付费吗?
不需要。CaptchaAI 按并发线程数计费,不按数量计费。BASIC($15/月,5 线程)能同时提交 5 个验证码,线程内不限次数,超出部分排队,不加收费用。
第二个验证码提交后才渲染,怎么抓取?
每次表单提交后重新扫描页面,用 WebDriverWait(Selenium)或 page.waitForSelector(Puppeteer)等待新控件,不要只在加载时检测一次。
每个验证码的 sitekey 都需要单独提取吗?
需要。不同验证码通常各自使用独立的 sitekey,即使类型相同,也要按控件单独提取,不要假设共用一个。
下一步
注册 CaptchaAI 获取 API Key,几行代码即可把检测、并行提交和 token 回填接入现有脚本。