JavaのSeleniumでCAPTCHA APIを使用する

著者:solvethree読了時間:2分

まずトークンをリクエストし、ページに移動する前に、同じプロキシと返された userAgent および secChUa を Chrome に設定します。solution.token をアプリケーションの CAPTCHA フィールドに設定してフォームを送信し、タスクが ready になるだけでなく、アプリケーションが成功状態になるまで待機してください。

これらの例では、Java の REST ヘルパーと Selenium ChromeDriver を使用します。CAPTCHA の自動検出やブラウザプラグインは使用しません。

このページの内容

Java プロジェクトと対象サイトを準備する

  1. 1.

    プロジェクトを設定する

    Java 21 以降を使用してください。Maven プロジェクトでは、Java のコンパイルレベルを 21 に設定し、Gson 2.15.0 と org.seleniumhq.selenium:selenium-java:4.52.0 を追加します。Selenium の Chrome ドキュメントに従って、Chrome と ChromeDriver を設定してください。

  2. 2.

    両方の Java ファイルを保存する

    最初の例を src/main/java/Solve.java、2 つ目を src/main/java/BrowserSolve.java として保存してください。両方を同じパッケージに配置します。提供されているファイルにはパッケージ宣言がありません。BrowserSolve が呼び出すのは Solve.api であり、Solve.main ではありません。

  3. 3.

    サイトプロファイルの有効化

    solvethree コンソールで対象ホスト名を設定し、そのプロファイルがワークスペース向けに有効になっていることを確認してください。ホスト名とアクションを送信しても、プロファイルは作成されません。サイトプロファイルの設定を参照してください。

  4. 4.

    環境を設定する

    BrowserSolve を実行するプロセスに、以下の 3 つの環境変数を設定してください。ワークスペースの API キーは、ページやブラウザの JavaScript ではなく、信頼できる Java プロセス内に保持してください。

BrowserSolve の環境変数
BrowserSolve の環境変数
変数値
SOLVETHREE_API_KEYワークスペースの API キー。
SOLVETHREE_PROXYIP 許可リスト方式の HTTP プロキシ URL。ユーザー名やパスワード、パス、クエリ、フラグメントを含めないでください。
TARGET_URLフォームを含むアプリケーションページの完全な URL。

この例では Chrome のプロキシ URL 経由で認証情報を渡せないため、IP 許可リスト方式のプロキシが必要です。同じプロキシ値を、タスク API と Chrome に渡します。

API リクエスト用の Solve.java を追加する

Solve.api は https://api.solvethree.com/v1 に認証付きリクエストを送信し、成功レスポンスを解析する前に HTTP ステータスを確認します。また、各リクエストの時間を、ローカルで設定した期限の残り時間に制限します。

Java 21+ · Solve.java(Gson依存関係)
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.UUID;

public class Solve {
    static final HttpClient HTTP = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(15)).build();
    static final String KEY = System.getenv("SOLVETHREE_API_KEY");

    static JsonObject api(String path, JsonObject body, long deadline) throws Exception {
        long remaining = deadline - System.nanoTime();
        if (remaining <= 0) throw new IllegalStateException("Keep the task ID and resume polling");
        var builder = HttpRequest.newBuilder(URI.create("https://api.solvethree.com/v1" + path))
            .timeout(Duration.ofNanos(Math.min(remaining, Duration.ofSeconds(30).toNanos())))
            .header("Authorization", "Bearer " + KEY)
            .header("Content-Type", "application/json");
        if (body == null) builder.GET();
        else builder.POST(HttpRequest.BodyPublishers.ofString(body.toString()));
        var response = HTTP.send(builder.build(), HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() < 200 || response.statusCode() >= 300)
            throw new IllegalStateException("API HTTP " + response.statusCode());
        return JsonParser.parseString(response.body()).getAsJsonObject();
    }
    public static void main(String[] args) throws Exception {
        String proxy = System.getenv("SOLVETHREE_PROXY");
        if (KEY == null || proxy == null) throw new IllegalArgumentException("Set the API key and proxy");
        var input = new JsonObject();
        input.addProperty("site", "accounts.example.com");
        input.addProperty("action", "login");
        input.addProperty("proxy", proxy);
        input.addProperty("requestId", UUID.randomUUID().toString());
        // Save input before creation if the task must be recovered after restart.
        long deadline = System.nanoTime() + Duration.ofSeconds(180).toNanos();
        var task = api("/tasks?waitMs=0", input, deadline);
        while (task.get("status").getAsString().equals("pending") || task.get("status").getAsString().equals("solving")) {
            String id = URLEncoder.encode(task.get("id").getAsString(), StandardCharsets.UTF_8);
            task = api("/tasks/" + id + "?waitMs=15000", null, deadline);
            if (task.get("status").getAsString().equals("pending") || task.get("status").getAsString().equals("solving"))
                Thread.sleep(1000);
        }
        if (!task.get("status").getAsString().equals("ready"))
            throw new IllegalStateException("Task failed: " + task.get("errorCode")
                + ": " + task.get("error"));
        var solution = task.getAsJsonObject("solution");
        // Submit solution.token with userAgent/secChUa and the original proxy.
        System.out.println("Ready task: " + task.get("id").getAsString());
    }
}

単独実行用の main メソッドは、タスクの作成とポーリングを示します。そこで使われる accounts.example.com ホスト名と login アクションはプレースホルダーです。Selenium との連携では、このファイルをヘルパーとして使用し、代わりに BrowserSolve を実行してください。両方の main メソッドを実行すると、別々のタスクが作成されます。

reCAPTCHA リクエストには、site に指定するホスト名、アプリケーションの action、および proxy と proxyCandidates のいずれか一方だけが必要です。type や url は追加しないでください。recaptchaType を省略すると、これらの例で使用する enterprise-v3 が選択されます。

準備できた解答の識別情報で Chrome を起動する

BrowserSolve は TARGET_URL から site を導出し、タスクを作成して、ステータスが pending または solving の間はポーリングします。ブラウザで使用する解答が得られるのは、ready のタスクだけです。

Java · 準備完了のソリューションをChromeに渡す
// Save as BrowserSolve.java beside Solve.java from the Java REST quickstart.
// Add Selenium Java 4.52.0 and Gson 2.15.0 to your Maven dependencies; Java 21+.
import com.google.gson.JsonObject;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Map;
import java.util.UUID;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class BrowserSolve {
    public static void main(String[] args) throws Exception {
        String target = System.getenv("TARGET_URL");
        String proxy = System.getenv("SOLVETHREE_PROXY");
        URI proxyUri = URI.create(proxy);
        if (proxyUri.getUserInfo() != null) throw new IllegalArgumentException("Use an IP-allowlisted proxy");
        var input = new JsonObject();
        input.addProperty("site", URI.create(target).getHost());
        input.addProperty("action", "login");
        input.addProperty("proxy", proxy);
        input.addProperty("requestId", UUID.randomUUID().toString());
        long deadline = System.nanoTime() + Duration.ofSeconds(180).toNanos();
        var task = Solve.api("/tasks?waitMs=0", input, deadline);
        while (task.get("status").getAsString().matches("pending|solving")) {
            String id = URLEncoder.encode(task.get("id").getAsString(), StandardCharsets.UTF_8);
            task = Solve.api("/tasks/" + id + "?waitMs=15000", null, deadline);
            if (task.get("status").getAsString().matches("pending|solving")) Thread.sleep(1000);
        }
        if (!task.get("status").getAsString().equals("ready")) throw new IllegalStateException("Task failed");
        var solution = task.getAsJsonObject("solution");
        var options = new ChromeOptions();
        options.addArguments("--proxy-server=" + proxy, "--user-agent=" + solution.get("userAgent").getAsString());
        var browser = new ChromeDriver(options);
        try {
            browser.executeCdpCommand("Network.enable", Map.of());
            browser.executeCdpCommand("Network.setExtraHTTPHeaders", Map.of("headers",
                Map.of("sec-ch-ua", solution.get("secChUa").getAsString())));
            browser.get(target);
            var wait = new WebDriverWait(browser, Duration.ofSeconds(20));
            var field = wait.until(driver -> driver.findElement(By.name("g-recaptcha-response")));
            browser.executeScript("arguments[0].value=arguments[1]; arguments[0].dispatchEvent(new Event('input',{bubbles:true}));",
                field, solution.get("token").getAsString());
            browser.findElement(By.cssSelector("button[type=submit]")).click();
            wait.until(driver -> driver.findElement(By.id("success"))); // Your application's success marker.
        } finally { browser.quit(); }
    }
}

順序が重要です。ChromeOptions でプロキシと返されたユーザーエージェントを設定し、次に ChromeDriver が CDP 経由で返された sec-ch-ua ヘッダーを設定してから、browser.get(target) を実行します。無関係な識別情報で先にページを開き、トークン送信時にだけ変更しないでください。

この例では、Chrome が候補から選ぶことなく同じプロキシを使用できるよう、意図的に単一の proxy を送信しています。ターゲット URL は、タスクで使用するホスト名とアクションに整合させてください。

BrowserSolve の実行前にフォームに合わせて調整する

このブラウザーの例は、CAPTCHA レスポンスフィールド、送信ボタン、成功を示す要素を持つ、自分が管理するフォームを対象としています。Maven の依存関係をクラスパスに含めて BrowserSolve.main を実行する前に、これらのアプリケーション固有の値を変更してください。

  • action: login を、この送信でアプリケーションが使用するアクションに置き換えてください。
  • g-recaptcha-response: フォームが実際に送信するフィールド名を確認してください。この例では solution.token を代入し、バブリングする input イベントをディスパッチします。
  • button[type=submit]: このセレクターで対象フォームの送信ボタンを特定できない場合は、置き換えてください。
  • success: この要素 ID を、今回の送信が成功した後にのみ現れる状態を示すものに置き換えてください。

フィールドの設定とイベントのディスパッチは、汎用的な CAPTCHA コールバックではありません。アプリケーションのフォーム処理がそのフィールドやイベントを使用しない場合は、実際のバインディングに合わせて送信手順を調整してください。送信リクエストを調べ、タスク ID や古いトークンではなく solution.token が含まれていることを確認してください。

最後の WebDriver の待機処理では、アプリケーションの成功を示す要素を確認します。このマーカーは慎重に選んでください。送信前から存在する要素では、新しいリクエストについて何も証明できません。タスクが ready になったことは、solvethree が解を生成したことを意味します。ターゲットに受け入れられたかどうかは、アプリケーションのレスポンスで確認します。

不要な追加タスクを作成せずに失敗に対処する

API が成功以外の HTTP レスポンスを返す。

HTTP レスポンスはタスクのステータスとは別に確認してください。Solve.api は、成功ペイロードとして扱う前に例外をスローします。

試してみましょう: 再試行する前に、リクエストエラーの原因を調べてください。無効な入力を変更せずに繰り返し送信するのではなく、リクエストフィールドと有効なサイトプロファイルを確認してください。

タスクが failed になる。

タスクのレスポンスから errorCode と error を読み取ってください。BrowserSolve の汎用例外ではこれらは表示されませんが、Solve.java の単体実行例では表示されます。

試してみましょう: そのタスクのポーリングを停止し、エラー処理でこれらのフィールドを表示してください。解決に失敗した場合は返金されます。すべての失敗をクレジットの問題だと決めつけないでください。

作成がタイムアウトする、またはローカルの期限に達する。

すでにタスク ID を取得しているか、元のリクエストボディと requestId を保存しているかを確認してください。接続のタイムアウトだけでは、作成に失敗したとは断定できません。

試してみましょう: タスク ID を使って取得を再開するか、同じ requestId と同一の入力で作成結果を復旧してください。再起動後も復旧できるようにする必要がある場合は、作成前にボディを保存してください。提供された main メソッドを再実行すると、新しい requestId が生成されます。

タスクは ready だが、アプリケーションが送信を拒否する。

送信されるトークン、アクション、ターゲット URL、プロキシ、ユーザーエージェント、sec-ch-ua を調べてください。新しく代入したフィールド値をアプリケーションが実際に送信したことを確認してください。

試してみましょう: 追加の解決に料金を支払う前に、値の不一致や送信時のバインディングを修正してください。成功時のプロキシと返された識別情報は、必ず組み合わせて使用してください。

API の期限である 180 秒とアプリケーションの待機時間である 20 秒は、例で選んだローカルの制限であり、サービスの保証ではありません。API エラーの詳細については、CAPTCHA API エラーを参照してください。

最初のタスクを実行しましょう。

$5のワークスペースクレジットで始められます。

ワークスペースを作成
JavaのSeleniumでCAPTCHA APIを使用する · solvethree