Os Progressive Web Apps apresentam desafios CAPTCHA únicos. Eles usam renderização do lado do cliente, Service Workers e navegação de página única – o que significa que os CAPTCHAs são carregados dinamicamente, em vez de com o HTML inicial.CaptchaAIcuida da solução, mas você precisa da estratégia de detecção correta para capturar CAPTCHAs que são injetados no DOM após o carregamento da página.
Este guia cobre a detecção, extração e resolução de CAPTCHAs em contextos PWA com Playwright e CaptchaAI.
Por que os PWAs são diferentes
Os sites tradicionais servem CAPTCHAs na resposta HTML inicial. Os PWAs diferem em vários aspectos importantes:
| Aspecto | Sítio Tradicional | PWA |
|---|---|---|
| Carregando CAPTCHA | No HTML inicial | Renderizado por JavaScript após o carregamento da página |
| Navegação na página | Recarregar página inteira | Roteamento do lado do cliente (sem recarga) |
| Trabalhador de serviço | Não presente | Armazena recursos em cache, pode interceptar solicitações |
| Disponibilidade do DOM | Imediato | Depois da renderização do framework |
| Solicitações de rede | Direto | Pode ser interceptado pelo Service Worker |
Etapa 1: aguarde a renderização dinâmica de CAPTCHA
O maior erro é tentar extrair as chaves do site antes que a estrutura PWA renderize o widget CAPTCHA. Use observadores de mutação ou sinais específicos da estrutura:
// pwa_captcha_detector.js — Playwright script
const { chromium } = require('playwright');
const axios = require('axios');
const API_KEY = 'YOUR_API_KEY';
async function detectCaptchaInPWA(page) {
// Wait for the PWA app shell to render
await page.waitForLoadState('networkidle');
// Use MutationObserver to detect dynamically loaded CAPTCHAs
const captchaInfo = await page.evaluate(() => {
return new Promise((resolve) => {
// Check if CAPTCHA is already present
const existing = document.querySelector('.g-recaptcha, .cf-turnstile');
if (existing) {
resolve({
type: existing.classList.contains('g-recaptcha')
? 'recaptcha_v2' : 'turnstile',
sitekey: existing.getAttribute('data-sitekey'),
pageurl: window.location.href,
});
return;
}
// Watch for CAPTCHA elements added dynamically
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType !== 1) continue;
const captcha = node.matches?.('.g-recaptcha, .cf-turnstile')
? node
: node.querySelector?.('.g-recaptcha, .cf-turnstile');
if (captcha) {
observer.disconnect();
resolve({
type: captcha.classList.contains('g-recaptcha')
? 'recaptcha_v2' : 'turnstile',
sitekey: captcha.getAttribute('data-sitekey'),
pageurl: window.location.href,
});
return;
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
// Timeout after 15 seconds
setTimeout(() => {
observer.disconnect();
resolve(null);
}, 15000);
});
});
return captchaInfo;
}
async function main() {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://example-pwa.com/login');
const captcha = await detectCaptchaInPWA(page);
if (!captcha) {
console.log('No CAPTCHA detected');
await browser.close();
return;
}
console.log(`Detected ${captcha.type}: ${captcha.sitekey}`);
// Solve with CaptchaAI
const token = await solveCaptcha(captcha);
console.log(`Token: ${token.substring(0, 50)}...`);
// Inject token
await injectToken(page, captcha.type, token);
// Submit form
await page.click('button[type="submit"]');
await page.waitForNavigation({ waitUntil: 'networkidle' });
console.log('Form submitted');
await browser.close();
}
async function solveCaptcha(captcha) {
const params = {
key: API_KEY,
pageurl: captcha.pageurl,
json: '1',
};
if (captcha.type === 'recaptcha_v2') {
params.method = 'userrecaptcha';
params.googlekey = captcha.sitekey;
} else {
params.method = 'turnstile';
params.sitekey = captcha.sitekey;
}
const submit = await axios.get(
'https://ocr.captchaai.com/in.php', { params }
);
if (submit.data.status !== 1) throw new Error(submit.data.request);
const taskId = submit.data.request;
for (let i = 0; i < 30; i++) {
await new Promise((r) => setTimeout(r, 5000));
const poll = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: taskId, json: '1' },
});
if (poll.data.status === 1) return poll.data.request;
if (poll.data.request !== 'CAPCHA_NOT_READY') {
throw new Error(poll.data.request);
}
}
throw new Error('Timeout');
}
async function injectToken(page, type, token) {
if (type === 'recaptcha_v2') {
await page.evaluate((t) => {
document.getElementById('g-recaptcha-response').value = t;
try {
const clients = ___grecaptcha_cfg.clients;
Object.keys(clients).forEach((k) => {
Object.keys(clients[k]).forEach((j) => {
if (clients[k][j]?.callback) clients[k][j].callback(t);
});
});
} catch (e) {}
}, token);
} else {
await page.evaluate((t) => {
const input = document.querySelector('[name="cf-turnstile-response"]');
if (input) input.value = t;
const cb = document.querySelector('.cf-turnstile')
?.getAttribute('data-callback');
if (cb && typeof window[cb] === 'function') window[cb](t);
}, token);
}
}
main().catch(console.error);
Etapa 2: lidar com o cache do Service Worker
Service Workers podem armazenar scripts CAPTCHA em cache, levando a widgets obsoletos. Ignore o cache quando necessário:
// Intercept and validação autorizada Service Worker cache for CAPTCHA scripts
await page.route('**/recaptcha/**', (route) => {
route.continue({ headers: { ...route.request().headers(), 'Cache-Control': 'no-cache' } });
});
await page.route('**/turnstile/**', (route) => {
route.continue({ headers: { ...route.request().headers(), 'Cache-Control': 'no-cache' } });
});
Etapa 3: lidar com a navegação do lado do cliente
Os PWAs usam roteamento do lado do cliente. Navegar para uma rota protegida por CAPTCHA não aciona o carregamento da página. Monitore as alterações de rota:
// Monitor PWA route changes for new CAPTCHAs
await page.evaluate(() => {
const originalPushState = history.pushState;
history.pushState = function() {
originalPushState.apply(this, arguments);
window.dispatchEvent(new Event('pwa-route-change'));
};
});
page.on('console', async (msg) => {
// React to route changes if needed
});
// Or wait for specific route
await page.waitForURL('**/checkout', { waitUntil: 'networkidle' });
// Then detect CAPTCHA on the new route
const captcha = await detectCaptchaInPWA(page);
Etapa 4: Alternativa Python com Selenium
# pwa_captcha_selenium.py
import time
import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
API_KEY = "YOUR_API_KEY"
driver = webdriver.Chrome()
driver.get("https://example-pwa.com/login")
# Wait for PWA to render CAPTCHA
wait = WebDriverWait(driver, 20)
captcha_el = wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".g-recaptcha, .cf-turnstile"))
)
sitekey = captcha_el.get_attribute("data-sitekey")
pageurl = driver.current_url
is_turnstile = "cf-turnstile" in captcha_el.get_attribute("class")
# Submit to CaptchaAI
params = {"key": API_KEY, "pageurl": pageurl, "json": "1"}
if is_turnstile:
params["method"] = "turnstile"
params["sitekey"] = sitekey
else:
params["method"] = "userrecaptcha"
params["googlekey"] = sitekey
resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
task_id = resp.json()["request"]
# Poll
for _ in range(30):
time.sleep(5)
poll = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": "1",
})
if poll.json().get("status") == 1:
token = poll.json()["request"]
break
else:
raise TimeoutError("CAPTCHA not solved")
# Inject token
driver.execute_script(f"""
document.getElementById('g-recaptcha-response').value = '{token}';
""")
driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]').click()
print("Form submitted")
driver.quit()
Verificações de invalidação de token
- Invalidar tokens CAPTCHA armazenados em cache quando alterações de rota, alterações de estado de autenticação ou atualizações de service workers alterarem o contexto da página.
- Armazene apenas o estado mínimo necessário para reconectar o token resolvido à visualização ativa.
- Verifique a atualização do token logo antes do envio, em vez de assumir que uma solução em segundo plano permanece válida.
Solução de problemas
| Problema | Causa | Correção |
|---|---|---|
| O elemento CAPTCHA nunca aparece | PWA ainda não renderizou a rota | Use waitForSelector com timeout estendido; garantir que o roteamento do lado do cliente seja concluído |
| Sitekey obsoleto após navegação | Service Worker serviu HTML em cache | Ignorar cabeçalhos de cache para recursos relacionados ao CAPTCHA |
| Retorno de chamada de envio controlado ao endpoint QA não encontrado | A estrutura PWA gerencia o estado de maneira diferente | Verifique o gerenciamento de estado React/Vue/Angular; acionar atualização do estado do formulário |
| O envio do formulário não envia token | O manipulador de formulário SPA lê o estado do componente, não o DOM | Atualize também o estado da estrutura (por exemplo, React ref, propriedade reativa Vue) |
Perguntas frequentes
Os PWAs usam tipos de CAPTCHA diferentes dos sites normais?
Os PWAs usam os mesmos widgets reCAPTCHA, Turnstile e outros widgets CAPTCHA. A diferença é o tempo – eles são carregados dinamicamente.
Os Service Workers podem bloquear a resolução de CAPTCHA?
Os Service Workers podem armazenar scripts CAPTCHA em cache, mas você pode contornar isso com cabeçalhos que impedem o cache ou desabilitando o Service Worker durante a automação.
O CaptchaAI lida com tokens específicos do PWA de maneira diferente?
Não. Os tokens são idênticos quer o CAPTCHA tenha sido veiculado em um PWA ou em uma página tradicional. CaptchaAI usa sitekey e URL – ambos são iguais.
Artigos relacionados
- Como resolver o retorno de chamada do Recaptcha V2 usando API
- Comparação de Geetest vs Cloudflare Turnstile
- Cloudflare Turnstile 403 Após correção do token
Próximas etapas
Comece a resolver CAPTCHAs em PWAs –obtenha sua chave API CaptchaAIe integre a detecção dinâmica à sua automação.
Guias relacionados: