👻

JavaでのCAPTCHA突破となぜこれを避けるべきか

2022/10/06に公開

この投稿では、[Re]captchaをJavaといくつかのサードパーティAPIで解決する方法と、そもそものところそれらを避けるべき理由を説明します。
Pythonコード(+ キャプチャ API) については、この投稿を参照してください。

Captcha突破

CAPTCHAとは“Completely Automated Public Turing test to tell Computers and Humans Apart”( 人間とマシンを判別するチューリングテスト)の略称です。CaptchaはBOTがウェブサイトやアプリケーションにアクセスして何らかのアクションを実行するのを防ぐために使用されます。

最後の1つは最もよく使われているCAPTCHAメカニズムである Google ReCaptcha v2 です。そのため、これらのCAPTCHAを「破る」方法を見ていきます。

ユーザーがするべきことは、チェックボックスをクリックするだけです。すると、当該サービスが多くの要因を分析して、アクセスしているのが実際のユーザーなのかBOTなのかを判断します。 Googleは明らかな理由でこれを開示していませんが、多くの憶測がなされています。:

  • クリック行動分析:ユーザーがどこをクリックしたか?、カーソルの加速など。
  • ブラウザのフィンガープリント
  • ロケーション履歴をクリックします (常に中央をまっすぐクリックするのか、それとも通常の- ユーザーのようにランダムにクリックするのか?
  • ブラウザの履歴と Cookie

最初のCAPTCHAのような古いCAPTCHAの場合、光学文字認識と最近の機械学習フレームワークが優れた突破精度を提供します (ときには人間よりも優れていることもあります…) が、Recaptcha v2の場合、サードパーティのサービスを使用するのが最も簡単で正確な方法になります。

多くの企業が、本物の人間のオペレーターを使用してCAPTCHAを解決するCAPTCHA突破API を提供しています。特にお勧めはしませんが、2captcha.comは使いやすく信頼性も高いですが、比較的高価です (recaptcha1000件当たり$2.99)。

内容的には、これらのAPIには特定のサイトキーとターゲットサイトのURLが必要です。この情報を使用して、人間のオペレーターにCAPTCHAを解決させることができます。

技術的には、Recaptchaの課題とはいくつかの魔法のような Javascriptコードと隠し入力を含むiFrameです。クリックするか、画像の問題を解決することで課題を「解決」すると、隠された入力欄に有効なトークンが入力されます。

私たちが興味を持っているのはまさにこのトークンで、2captcha APIがそれを送り返します。次に、非表示の入力欄にこのトークンを入力し、フォームを送信する必要があります。

まず、2captcha.comでアカウントを作成create an account on 2captcha.comし、資金を追加してください。その後、メインダッシュボードにAPI キーが表示されます。

1 つの入力欄と突破するべきRecaptchaのある単純なフォームを使用して、サンプルウェブページwebpageをセットアップしました。

ヘッドレスモードでChromeを使用してこのフォームを投稿し、HtmlUnitを使用して2captchaへのAPIコールを行います (これには他のHTTPクライアントを使うこともできます)。それでは、コーディングしましょう。

final String API_KEY = "YOUR_API_KEY";
final String API_BASE_URL = "http://2captcha.com/";
final String BASE_URL = "https://www.javawebscrapingsandbox.com/captcha";
WebClient client = new WebClient();
client.getOptions().setJavaScriptEnabled(false);
client.getOptions().setCssEnabled(false);
client.getOptions().setUseInsecureSSL(true);
java.util.logging.Logger.getLogger("com.gargoylesoftware").setLevel(Level.OFF); // replace with your own chromdriver path 
final String chromeDriverPath = "/usr/local/bin/chromedriver";
System.setProperty("webdriver.chrome.driver", chromeDriverPath);
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless", "--disable-gpu", "--windowsize=1920,1200", "--ignore-certificate-errors", "--silent");
options.addArguments("--user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/60.0.3112.113 Chrome/60.0.3112.113 Safari/537.36");
WebDriver driver = new ChromeDriver(options);
driver.get(BASE_URL);

WebDriverとWebClientの両方をインスタンス化するボイラープレートコードと、API URLおよびキーを次に示します。

次に、こちらhereに記載されているように、サイトキー、APIキー、およびウェブサイトのURLを使用して2captcha API を呼び出す必要があります。API は次の形式で応答することになっています。: OK|123456

String siteId = "";
WebElement elem = driver.findElement(By.xpath("//div[@class='g-recaptcha']"));
try {
    siteId = elem.getAttribute("data-sitekey");
} catch (Exception e) {
    System.err.println("Catpcha's div cannot be found or missing attribute data-sitekey");
    e.printStackTrace();
}
String QUERY = String.format("%sin.php?key=%s&method=userrecaptcha&googlekey=%s&pageurl=%s&here=now", API_BASE_URL, API_KEY, siteId, BASE_URL);
Page response = client.getPage(QUERY);
String stringResponse = response.getWebResponse().getContentAsString();
String jobId = "";
if (!stringResponse.contains("OK")) {
    throw new Exception("Error with 2captcha.com API, received : " + stringResponse);
} else {
    jobId = stringResponse.split("\\|")[1];
}

ジョブIDを取得したので、ドキュメントで説明しているように別のAPIルートをループして、ReCaptchaがいつ解決され、トークンを取得するかを知る必要があります。まだ準備ができていないときにはCAPCHA_NOT_READYを、準備ができているときは OK|TOKENが返ってきます。

boolean captchaSolved = false;
while (!captchaSolved) {
    response = client.getPage(String.format("%sres.php?key=%s&action=get&id=%s", API_BASE_URL, API_KEY, jobId));
    if (response.getWebResponse().getContentAsString().contains("CAPCHA_NOT_READY")) {
        Thread.sleep(3000);
        System.out.println("Waiting for 2Captcha.com ...");
    } else {
        captchaSolved = true;
        System.out.println("Captcha solved !");
    }
}
String captchaToken = response.getWebResponse().getContentAsString().split("\\|")[1];

私の経験によると最大1分かかる場合があることに注意してください。まれにCAPTCHAが突破されない場合があるため、ループにセーフガード/タイムアウトを実装することをお勧めします。

マジックトークンを取得したので、必要なことは非表示の入力欄を見つけ、トークンを入力し、フォームを送信するだけです。

Selenium API は非表示の入力欄を埋めることができないため、送信ボタンをクリックできるように、DOMを操作して入力欄を表示させ、入力、そして再び非表示にする必要があります。:

JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("document.getElementById('g-recaptcha-response').style.display = 'block';");
WebElement textarea = driver.findElement(By.xpath("//textarea[@id='g-recaptcha-response']"));
textarea.sendKeys(captchaToken);
js.executeScript("document.getElementById('g-recaptcha-response').style.display = 'none';");
driver.findElement(By.id("name")).sendKeys("Kevin");
driver.getPageSource();
driver.findElement(By.id("submit")).click();
if (driver.getPageSource().contains("your captcha was successfully submitted")) {
    System.out.println("Captcha successfuly submitted !");
} else {
    System.out.println("Error while submitting captcha");
}

以上 :-)。Javaコード全体はこちらhereで確認できます。

通常、ウェブサイトはHTTPリクエストごとにReCaptchaを使うのではなく、疑わしいリクエストやアカウント作成などの特定のアクションに対してのみ使用します。同じIP アドレスまたは同じユーザーエージェントを使用したリクエストが多すぎるのか、または1 秒あたりのリクエスト数が多すぎるためにウェブサイトが[Re]captchaを表示しているのかどうか常に解明する必要があります。

ご覧のとおり、「Recaptchaの突破」は非常に時間がかかるため、この問題を「解決」する最善の方法は、そもそも最初からCAPTCHAチャを回避することです!そのためには、ブロックされずにウェブサイトをスクレイピングする方法How to scrape websites without getting blockedの記事を読むことをお勧めします。ぜひチェックしてみてください!

CAPTCHAを表示される可能性を減らすことは、実はそれを解決するよりも優れており、安価ではるかに高速です。ウェブページには100%の確率でCAPTCHAが表示されるため、それが不可能な場合もありますが、多くの場合はスクレーパーを賢く使うことでこれを回避できます。

Discussion