Scarnox · Blog

Playwright と Puppeteer(Node.js)で PopularCaptcha を解決する

ブラウザ自動化は、PopularCaptcha が現れた瞬間に止まります。スクリプトから画像のグリッドをクリックして進むのは遅く、壊れやすいものです。解決 API からトークンを取得してページに入れるなら、1回のリクエストで済み、スクリプトの残りは通常どおり進みます。

このガイドでは、Node.js の Playwright と Puppeteer を扱います。どちらもフローは同じで、例ではプロキシなしの解決を使います。プロキシなしの解決は、Scarnox が自身のネットワーク上で実行します。PopularCaptcha は、Scarnox が今日解決する種別です。他のキャプチャ種別は近日公開予定のため、以下のコードは PopularCaptcha だけを対象としています。

フロー

  1. ページから sitekey を読み取る。
  2. ブロッキングする POST リクエスト1回で、Scarnox からトークンを取得する。
  3. トークンをレスポンスフィールドに入れるか、サイトのコールバックに渡してから、フォームを送信する。

キーの設定

アカウントを作成して、クレジットカード不要の無料トライアルクレジットから始めてください。キーは環境変数に保持し、テストコードには決して書かないでください。

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,
}),

このようなウィジェットの見分け方と、トークンの返し方はインビジブルキャプチャのガイドで説明しています。

タイミングのヒント

自分のフローのテスト

より大きなものに向ける前に、自分が所有するフォームに対して例を実行してください。三つのことを確認します。トークンが受け入れられること、フォームが送信されること、次のページが表示されることです。フローがエンドツーエンドで動いたら、CI をグリーンに保つために、実行ごとに本物の解決を1回だけ行う形にしてください。

無料で試す

アカウントを作成して、クレジットカード不要の無料トライアルクレジットから始めてください。フロー全体を組み込んで確認するには十分です。大量に解決した場合の費用は、現在の料金ページをご覧ください。

拒否されたトークンのデバッグ

サイトがトークンを拒否したら、他の何かを変える前に、一つのリクエストと一回のページ読み込みでフローを再現してください。次の順に確認します。

失敗した瞬間のページ HTML を await page.content() で保存してください。成功した実行の HTML と比べると、違いはすぐに分かることが多いです。たとえば、sitekey が違う、フィールドがない、ウィジェットが別のモードで描画された、といったものです。

次のステップ