Playwright と Puppeteer(Node.js)で PopularCaptcha を解決する
ブラウザ自動化は、PopularCaptcha が現れた瞬間に止まります。スクリプトから画像のグリッドをクリックして進むのは遅く、壊れやすいものです。解決 API からトークンを取得してページに入れるなら、1回のリクエストで済み、スクリプトの残りは通常どおり進みます。
このガイドでは、Node.js の Playwright と Puppeteer を扱います。どちらもフローは同じで、例ではプロキシなしの解決を使います。プロキシなしの解決は、Scarnox が自身のネットワーク上で実行します。PopularCaptcha は、Scarnox が今日解決する種別です。他のキャプチャ種別は近日公開予定のため、以下のコードは PopularCaptcha だけを対象としています。
フロー
- ページから sitekey を読み取る。
- ブロッキングする POST リクエスト1回で、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,
}),
このようなウィジェットの見分け方と、トークンの返し方はインビジブルキャプチャのガイドで説明しています。
タイミングのヒント
- トークンは送信の直前に取得してください。有効期限は約2分です。
- 解決リクエストには長めのタイムアウト、約185秒を設定してください。トークンの準備ができるまでブロックします。
- 解決の失敗は無料なので、リトライは時間だけがかかります。一時的な失敗はバックオフを付けてリトライしてください。方法はエラー処理とバックオフで説明しています。
- 多数のブラウザを同時に動かしていますか。同時実行の解決数に上限を設け、一つの遅いバッチが全体の実行を止めないようにしてください。
finallyブロックでブラウザを閉じてください。クラッシュしたテストが Chromium を起動したままにすると、同じマシン上の後続の解決がすべて遅くなります。
自分のフローのテスト
より大きなものに向ける前に、自分が所有するフォームに対して例を実行してください。三つのことを確認します。トークンが受け入れられること、フォームが送信されること、次のページが表示されることです。フローがエンドツーエンドで動いたら、CI をグリーンに保つために、実行ごとに本物の解決を1回だけ行う形にしてください。
無料で試す
アカウントを作成して、クレジットカード不要の無料トライアルクレジットから始めてください。フロー全体を組み込んで確認するには十分です。大量に解決した場合の費用は、現在の料金ページをご覧ください。
拒否されたトークンのデバッグ
サイトがトークンを拒否したら、他の何かを変える前に、一つのリクエストと一回のページ読み込みでフローを再現してください。次の順に確認します。
- 送った sitekey が、読み込んだページのウィジェットと一致している。
site_urlが、リダイレクト先ではなく、ウィジェットが存在するページである。- トークンが、ページが読み取るフィールドに入っているか、コールバックが呼び出されている。
- 送信が、解決から約2分以内に行われている。
失敗した瞬間のページ HTML を await page.content() で保存してください。成功した実行の HTML と比べると、違いはすぐに分かることが多いです。たとえば、sitekey が違う、フィールドがない、ウィジェットが別のモードで描画された、といったものです。