Scarnox · Blog

Resolver un PopularCaptcha con Playwright y Puppeteer (Node.js)

La automatización del navegador se detiene en cuanto aparece un PopularCaptcha. Hacer clic en una cuadrícula de imágenes desde un script es lento y frágil. Obtener un token de una API de resolución y colocarlo en la página cuesta una sola petición, y el resto de tu script sigue como de costumbre.

Esta guía cubre Playwright y Puppeteer con Node.js. El flujo es el mismo en ambos, y los ejemplos usan resoluciones sin proxy, que Scarnox ejecuta en su propia red. PopularCaptcha es el tipo que Scarnox resuelve hoy. Otros tipos de captcha llegarán pronto, así que el código de abajo es solo para PopularCaptcha.

El flujo

  1. Lee la sitekey de la página.
  2. Pide a Scarnox un token con una sola petición POST bloqueante.
  3. Coloca el token en el campo de respuesta, o pásalo al callback del sitio, y luego envía el formulario.

Configura la clave

Crea una cuenta y empieza con crédito de prueba gratis, sin tarjeta. Guarda la clave en una variable de entorno, nunca en el código de pruebas:

export SCARNOX_KEY="scarnox_YOURAPIKEY"

Node 18 y versiones posteriores incluyen fetch, así que la llamada a la API no necesita paquetes adicionales.

Playwright

import { chromium } from "playwright";

const API_KEY = process.env.SCARNOX_KEY;

async function solve(sitekey, url) {
  const r = await fetch("https://api.scarnox.com/api/tasks/create", {
    method: "POST",
    headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ captcha_type: "PopularCaptchaTaskProxyless", site_key: sitekey, site_url: url }),
  });
  const data = await r.json();
  if (data.errorId !== 0) throw new Error(`${data.errorCode}: ${data.errorDescription}`);
  return data.solution.token;
}

const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("https://example.com/signup");

const sitekey = await page.getAttribute("[data-sitekey]", "data-sitekey");
const token = await solve(sitekey, page.url());

// Coloca el token en los dos campos de respuesta que el sitio podría leer.
await page.evaluate((t) => {
  for (const name of ["h-captcha-response", "g-recaptcha-response"]) {
    document.querySelectorAll(`[name="${name}"]`).forEach((el) => { el.value = t; });
  }
}, token);

await page.fill("#email", "you@example.com");
await page.click("button[type=submit]");

Fíjate en que site_url viene de page.url(). Esa es la página en la que vive el widget, que es lo que necesita el solver. La resolución ocurre después de que la página haya cargado, así que la sitekey ya está en el DOM.

Puppeteer

Puppeteer se diferencia solo en la API de selectores. Reutiliza la función solve de arriba y sustituye las llamadas de página:

const sitekey = await page.$eval("[data-sitekey]", (el) => el.dataset.sitekey);
const token = await solve(sitekey, page.url());

await page.evaluate((t) => {
  document.querySelectorAll('[name="h-captcha-response"],[name="g-recaptcha-response"]')
    .forEach((el) => { el.value = t; });
}, token);

Cuando el sitio usa un callback

Algunos sitios no leen el campo oculto. Esperan el callback JavaScript del widget. Busca la función indicada en el atributo data-callback del widget y llámala con el token:

await page.evaluate((t) => {
  const name = document.querySelector("[data-sitekey]")?.dataset.callback;
  if (name && typeof window[name] === "function") window[name](t);
}, token);

Si el sitio usa ambos mecanismos, rellena el campo y llama al callback. Hacer las dos cosas no causa ningún problema.

Widgets invisibles

Un widget invisible no tiene casilla. El sitio lo inicia desde su propio código, normalmente cuando el visitante pulsa enviar. Envía la misma petición con el campo opcional is_invisible en true:

body: JSON.stringify({
  captcha_type: "PopularCaptchaTaskProxyless",
  site_key: sitekey,
  site_url: url,
  is_invisible: true,
}),

La guía de captcha invisible explica cómo detectar estos widgets y cómo devolver el token.

Consejos de tiempos

Probar tu propio flujo

Ejecuta el ejemplo contra un formulario que sea tuyo antes de apuntarlo a algo más grande. Comprueba tres cosas: que el token se acepta, que el formulario se envía y que aparece la página siguiente. Cuando el flujo funcione de principio a fin, mantenlo en verde en CI con una única resolución real por ejecución.

Pruébalo gratis

Crea una cuenta y empieza con crédito de prueba gratis, sin tarjeta. Es suficiente para montar y comprobar todo el flujo. Para saber cuánto cuesta resolver a gran volumen, consulta las tarifas actuales.

Depurar un token rechazado

Cuando el sitio rechaza un token, reproduce el flujo con una petición y una carga de página antes de cambiar nada más. Comprueba estos puntos en orden:

Guarda el HTML de la página en el momento del fallo con await page.content(). Compararlo con una ejecución que funcionó suele mostrar la diferencia de un vistazo: una sitekey distinta, un campo que falta o un widget renderizado en otro modo.

Próximos pasos