Playwright와 Puppeteer에서 PopularCaptcha 풀기 (Node.js)
PopularCaptcha가 나타나는 순간 브라우저 자동화는 멈춥니다. 스크립트로 이미지 격자를 클릭하는 것은 느리고 깨지기 쉽습니다. 풀이 API에서 토큰을 받아 페이지에 넣는 것은 요청 한 번이면 되고, 스크립트의 나머지는 평소처럼 진행됩니다.
이 가이드는 Node.js의 Playwright와 Puppeteer를 다룹니다. 두 곳의 흐름은 같으며, 예제는 Scarnox가 자체 네트워크에서 실행하는 프록시리스 풀이를 사용합니다. Scarnox가 오늘 푸는 유형은 PopularCaptcha입니다. 다른 captcha 유형은 출시 예정이므로, 아래 코드는 PopularCaptcha 전용입니다.
흐름
- 페이지에서 sitekey를 읽습니다.
- 블로킹 POST 요청 한 번으로 Scarnox에 토큰을 요청합니다.
- 토큰을 응답 필드에 넣거나 사이트의 콜백에 넘긴 뒤 폼을 제출합니다.
키 설정
계정을 만들고 무료 체험 크레딧으로 카드 없이 시작하세요. 키는 테스트 코드가 아니라 환경 변수에 보관하세요.
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());
// 사이트가 읽을 수 있는 두 응답 필드에 모두 토큰을 넣습니다.
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()에서 온다는 점에 주목하세요. 위젯이 있는 페이지이며, 풀이기가 필요로 하는 값이 바로 그것입니다. 풀이는 페이지가 로드된 뒤에 실행되므로, sitekey는 이미 DOM에 있습니다.
Puppeteer
Puppeteer는 셀렉터 API만 다릅니다. 위의 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);
사이트가 콜백을 사용할 때
일부 사이트는 숨겨진 필드를 읽지 않고, 위젯의 JavaScript 콜백을 기다립니다. 위젯의 data-callback 속성에 이름이 적힌 함수를 찾아 토큰과 함께 호출하세요.
await page.evaluate((t) => {
const name = document.querySelector("[data-sitekey]")?.dataset.callback;
if (name && typeof window[name] === "function") window[name](t);
}, token);
사이트가 둘 다 사용한다면 필드를 설정하고 콜백도 호출하세요. 둘 다 해도 해가 없습니다.
보이지 않는 위젯
보이지 않는 위젯에는 체크박스가 없습니다. 사이트가 자체 코드에서 시작하며, 보통 방문자가 제출 버튼을 누를 때 시작합니다. 같은 요청을 보내되, 선택 필드인 is_invisible을 true로 설정하세요.
body: JSON.stringify({
captcha_type: "PopularCaptchaTaskProxyless",
site_key: sitekey,
site_url: url,
is_invisible: true,
}),
보이지 않는 captcha 가이드가 이런 위젯을 알아보는 방법과 토큰을 돌려주는 방법을 설명합니다.
타이밍 팁
- 제출 직전에 토큰을 가져오세요. 토큰은 약 2분 안에 만료됩니다.
- 풀이 요청에는 긴 타임아웃, 약 185초를 주세요. 토큰이 준비될 때까지 블로킹합니다.
- 실패한 풀이는 무료이므로, 재시도는 시간만 듭니다. 일시적인 실패는 오류 처리와 백오프에서 설명한 대로 백오프와 함께 재시도하세요.
- 브라우저를 여러 개 동시에 돌리나요? 동시에 실행하는 풀이 수에 상한을 두세요. 그러면 느린 배치 하나가 나머지 실행을 멈추지 않습니다.
- 브라우저는
finally블록에서 닫으세요. 충돌한 테스트가 Chromium을 실행 상태로 남기면, 같은 기기에서 이후의 모든 풀이가 느려집니다.
내 흐름 테스트하기
더 큰 대상에 적용하기 전에, 예제를 직접 소유한 폼에서 먼저 실행하세요. 세 가지를 확인하세요. 토큰이 받아들여지는지, 폼이 제출되는지, 다음 페이지가 나타나는지. 흐름이 끝까지 작동하면, 실행마다 실제 풀이 한 번으로 CI에서 녹색을 유지하세요.
무료로 체험하기
계정을 만들고 무료 체험 크레딧으로 카드 없이 시작하세요. 전체 흐름을 연결하고 확인하기에 충분합니다. 대량으로 풀 때의 비용은 현재 요금을 확인하세요.
거부된 토큰 디버깅하기
사이트가 토큰을 거부하면, 다른 것을 바꾸기 전에 요청 한 번과 페이지 로드 한 번으로 흐름을 재현하세요. 다음을 순서대로 확인하세요.
- 보낸 sitekey가 로드한 페이지의 위젯과 일치하나요?
site_url이 리다이렉트 대상이 아니라 위젯이 있는 페이지인가요?- 토큰이 페이지가 읽는 필드에 들어갔거나, 콜백이 호출되었나요?
- 제출이 풀이 후 약 2분 안에 일어났나요?
실패한 순간의 페이지 HTML을 await page.content()로 저장하세요. 그것을 성공한 실행과 비교하면 차이가 한눈에 보이는 경우가 많습니다. sitekey가 다르거나, 필드가 빠졌거나, 위젯이 다른 모드로 렌더링되었을 수 있습니다.