Login com reCAPTCHA de um lado, cadastro de newsletter com outro reCAPTCHA do outro, e às vezes mais um desafio no meio do checkout: quando a mesma página tem mais de um CAPTCHA, a resposta certa não é resolver um de cada vez e esperar o token antes de atacar o próximo. É detectar todos os widgets de uma só vez, enviar cada um em paralelo para a CaptchaAI e injetar o token de volta no callback correto. O erro mais comum nessa rotina é tratar dois CAPTCHAs como se tivessem a mesma sitekey — cada widget carrega a sua, e confundir uma com a outra é a causa número um de token caindo no formulário errado.
Antes de sair copiando código, vale alinhar o que muda em relação a resolver um único CAPTCHA:
- Detecção: você precisa mapear todos os widgets da página, não só o primeiro que aparece no DOM.
- Envio: as tarefas vão para a CaptchaAI em paralelo, não em fila sequencial — isso é o que mantém o tempo total baixo.
- Injeção: cada token só pode voltar para o widget de onde a sitekey saiu; misturar isso quebra o formulário.
- Tipos: reCAPTCHA e Turnstile na mesma tela usam
methoddiferentes na API — nunca envie os dois com o mesmo parâmetro.
Como detectar todos os CAPTCHAs da página
Antes de resolver, mapeie quantos widgets existem na página e qual sitekey cada um usa.
Python com 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']}")
Resultado esperado:
Found 2 CAPTCHAs on page
[0] sitekey: 6LcXyzABCDEF-login
[1] sitekey: 6LcAbcDEFGHI-signup
JavaScript com Puppeteer
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}`));
})();
Enviando e consultando todos os CAPTCHAs em paralelo
- Envie tudo de uma vez — todos os CAPTCHAs vão para a CaptchaAI sem esperar a resposta de um para enviar o próximo, nunca em fila sequencial.
- Consulte (poll) cada tarefa até o token voltar pronto para aquele widget específico.
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)
Resultado esperado:
[captcha-0] Submitted → task 71823456
[captcha-1] Submitted → task 71823457
[captcha-1] Solved
[captcha-0] Solved
Como injetar cada token no widget certo
- Cada widget reCAPTCHA tem sua própria
textareag-recaptcha-response, aninhada dentro do contêiner daquele widget específico. - O mapeamento entre token e widget é por índice, na ordem em que os widgets foram detectados — nunca por suposição de qual veio primeiro.
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);
Quando a página mistura tipos de CAPTCHA (reCAPTCHA + Turnstile)
- Login com um provedor e o restante do formulário com outro é o cenário mais comum de mistura de tipos.
- Detecte cada tipo separadamente pelo seletor CSS antes de enviar qualquer coisa para a CaptchaAI:
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 (
.g-recaptcha) vai commethod=userrecaptcha. - Cloudflare Turnstile (
.cf-turnstile) vai commethod=turnstile. - Nunca misture os dois no mesmo envio — mandar a sitekey do Turnstile com
method=userrecaptchadevolve erro da API em vez de um token.
Exemplo: cadastro com reCAPTCHA e Turnstile na mesma tela
Esse padrão é comum em fintechs e e-commerces no Brasil: a etapa de cadastro roda reCAPTCHA v2 no formulário principal e Cloudflare Turnstile na verificação de e-mail, os dois na mesma página. Em QA, isso costuma ser testado contra um ambiente de staging (staging.example.com) com workers hospedados na região sa-east-1 da AWS.
Alguns pontos que ajudam a dimensionar o teste:
- Cloudflare Turnstile normalmente resolve em menos de 10 segundos; o reCAPTCHA v2 tem teto de até 60 segundos sob alta concorrência. Resolver os dois em paralelo, e não em sequência, evita que o tempo total do teste some os dois tetos.
- Cada widget conta como uma tarefa independente — não existe desconto de tempo por estarem na mesma página, então a única forma de manter o pipeline de QA rápido é o envio em paralelo.
- Para volumes típicos de QA (poucos CAPTCHAs por execução), o plano BASIC (US$ 15/mês, 5 threads) já cobre o cenário sem fila de espera.
Boas práticas para páginas com vários CAPTCHAs
- Trate
sitekeycomo a chave primária do widget, nunca o índice de posição — a ordem dos elementos no DOM pode mudar entre deploys do front-end. - Envie tudo para a CaptchaAI em paralelo com
ThreadPoolExecutor(Python) ouPromise.all(JavaScript); resolver em sequência multiplica o tempo total sem necessidade. - Reescaneie a página depois de cada envio de formulário — um CAPTCHA que só renderiza após o passo anterior é comum em fluxos de várias etapas.
- Separe o
methodpor tipo de CAPTCHA antes de montar a requisição; nunca assuma que todos os widgets da página são do mesmo tipo. - Registre o
task_idde cada envio junto com o índice do widget — isso facilita depurar qual token pertence a qual CAPTCHA quando algo dá errado.
Perguntas frequentes
Cada widget de CAPTCHA na mesma página pode usar uma sitekey diferente?
Sim, é a regra. Extraia a sitekey por widget — nunca presuma que dois CAPTCHAs compartilham uma só.
Preciso de uma thread da CaptchaAI para cada CAPTCHA que resolvo ao mesmo tempo?
Sim, cada um ocupa uma thread até o token voltar:
- BASIC (US$ 15/mês, 5 threads) já cobre páginas com dois ou três desafios simultâneos.
- STANDARD (US$ 30/mês, 15 threads) dá mais margem para volumes maiores de QA.
O token foi injetado, mas o formulário continua bloqueado. O que verificar primeiro?
Se o callback foi disparado — muitos formulários só liberam o envio quando a função de data-callback é chamada; preencher a textarea sozinho não basta.
Um CAPTCHA que só aparece depois do primeiro envio do formulário muda alguma coisa no fluxo?
Sim: reescaneie a página após cada envio, com WebDriverWait (Selenium) ou page.waitForSelector (Puppeteer), para pegar o widget recém-renderizado antes de tentar resolvê-lo.
Solução de problemas
| Problema | Causa | Correção |
|---|---|---|
| Token injetado, mas formulário continua bloqueado | Callback não foi acionado | Verifique data-callback e chame a função com o token |
| Só o primeiro CAPTCHA é detectado | Segundo widget carrega de forma lenta (lazy) | Aguarde todos os iframes/widgets antes de escanear a página |
| Token aparece no widget errado | Índice trocado entre CAPTCHA e token | Mapeie tokens por sitekey, nunca por posição/índice |
ERROR_WRONG_GOOGLEKEY |
Sitekey extraída errada | Confira a sitekey no atributo src do iframe ou em data-sitekey |
Resolva vários CAPTCHAs por página sem trocar de integração
Obtenha sua chave de API em captchaai.com e use a mesma integração para reCAPTCHA e Turnstile, mesmo quando aparecem juntos na mesma página.