Tutoriais

Tratamento de CAPTCHA em aplicativos da Web progressivos (PWAs)

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

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:

Os comentários estão desativados para este artigo.