Scarnox · Blog

การแก้ PopularCaptcha ด้วย Playwright และ Puppeteer (Node.js)

automation ของเบราว์เซอร์จะหยุดชะงักทันทีที่ PopularCaptcha ปรากฏ การคลิกผ่านตารางรูปภาพจากสคริปต์ทำได้ช้าและเปราะบาง การรับ token จาก solving API และใส่ลงในหน้าใช้เพียงหนึ่ง request ส่วนที่เหลือของสคริปต์ทำงานตามปกติ

คู่มือนี้ครอบคลุม Playwright และ Puppeteer กับ Node.js flow เหมือนกันทั้งสองตัว และตัวอย่างใช้การ solve แบบ proxyless ซึ่ง Scarnox ทำงานบนเครือข่ายของตัวเอง PopularCaptcha คือประเภทที่ Scarnox solve ได้ในวันนี้ captcha ประเภทอื่น กำลังจะมาเร็ว ๆ นี้ ดังนั้นโค้ดด้านล่างใช้กับ PopularCaptcha เท่านั้น

flow

  1. อ่าน sitekey จากหน้า
  2. ขอ token จาก Scarnox ด้วย POST request หนึ่งครั้งที่บล็อกรอ
  3. ใส่ token ลงในฟิลด์ตอบกลับ หรือส่งให้ callback ของเว็บไซต์ จากนั้นส่งฟอร์ม

ตั้งค่า key

สร้างบัญชี และเริ่มด้วยเครดิตทดลองใช้ฟรี ไม่ต้องใช้บัตรเครดิต เก็บ key ไว้ในตัวแปรสภาพแวดล้อม ไม่ใส่ในโค้ดทดสอบ:

export SCARNOX_KEY="scarnox_YOURAPIKEY"

Node 18 ขึ้นไปมี fetch มาให้ การเรียก API จึงไม่ต้องติดตั้งแพ็กเกจเพิ่ม

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());

// ใส่ token ลงในฟิลด์ตอบกลับทั้งสองชื่อที่เว็บไซต์อาจอ่าน
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]");

สังเกตว่า site_url มาจาก page.url() นั่นคือหน้าที่ widget อยู่ ซึ่งเป็นสิ่งที่ solver ต้องการ การ solve เกิดขึ้นหลังจากหน้าโหลดแล้ว ดังนั้น sitekey จึงอยู่ใน DOM แล้ว

Puppeteer

Puppeteer แตกต่างจาก Playwright เฉพาะ API ของ selector ใช้ฟังก์ชัน solve ด้านบนซ้ำ และแทนที่การเรียกหน้า:

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);

เมื่อเว็บไซต์ใช้ callback

บางเว็บไซต์ไม่อ่านฟิลด์ที่ซ่อนอยู่ แต่รอ JavaScript callback ของ widget ให้หาฟังก์ชันที่ระบุใน attribute data-callback ของ widget แล้วเรียกด้วย token:

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

หากเว็บไซต์ใช้ทั้งสองแบบ ให้ตั้งค่าฟิลด์และเรียก callback การทำทั้งสองอย่างไม่เป็นอันตราย

widget แบบ invisible

widget แบบ invisible ไม่มีช่องติ๊ก เว็บไซต์เริ่มมันจากโค้ดของตัวเอง โดยปกติเมื่อผู้เข้าชมกดส่ง ส่ง request เดียวกันโดยเพิ่มฟิลด์ที่เป็นตัวเลือก is_invisible เป็น true:

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

คู่มือ captcha แบบ invisible อธิบายวิธีสังเกต widget เหล่านี้ และวิธีส่ง token กลับ

เคล็ดลับเรื่องเวลา

ทดสอบ flow ของคุณเอง

รันตัวอย่างกับฟอร์มที่คุณเป็นเจ้าของก่อนนำไปใช้กับงานที่ใหญ่กว่า ตรวจสามอย่าง: token ได้รับการยอมรับ ฟอร์มถูกส่ง และหน้าถัดไปปรากฏ เมื่อ flow ทำงานจนจบ ทำให้ CI ผ่านอยู่เสมอ ด้วยการ solve จริงหนึ่งครั้งต่อการรัน

ทดลองใช้ฟรี

สร้างบัญชี และเริ่มด้วยเครดิตทดลองใช้ฟรี ไม่ต้องใช้บัตรเครดิต ซึ่งเพียงพอสำหรับเชื่อมต่อและตรวจสอบ flow ทั้งหมด สำหรับค่าใช้จ่ายเมื่อใช้งานปริมาณมาก ดู ราคาปัจจุบัน

การ debug token ที่ถูกปฏิเสธ

เมื่อเว็บไซต์ปฏิเสธ token ให้ทำซ้ำ flow ด้วย request หนึ่งครั้งและการโหลดหน้าหนึ่งครั้งก่อนเปลี่ยนอย่างอื่น ตรวจตามลำดับนี้:

บันทึก HTML ของหน้าตอนที่ล้มเหลวด้วย await page.content() การเปรียบเทียบ HTML นั้นกับการรันที่ใช้งานได้มักแสดงความต่างได้ทันที เช่น sitekey ที่ต่างกัน ฟิลด์ที่หายไป หรือ widget ที่เรนเดอร์ในโหมดอื่น

ขั้นตอนถัดไป