AIを使って無料でreCAPTCHA v2を突破する(音声版、Python)

現在Bot対策で大体の所で使用されている手法としてCaptchaと呼ばれる技術が使われています。
これはプログラムでは識別が難しく、人間にはほぼ難なく識別できる視覚や聴覚を使用して人間が考えることで解決することが出来るというものになります。

前述した通り、これはあくまでも難しいだけであり技術の進歩は素晴らしく現在ではほぼ難なくAIで解決できるようになっています!

ですが、画像識別には難がありあまりにも識別が難しいレベル(人間にも難問)だったりすると識別率が著しく下がる場合があり、そういった場合には別で強化学習や専用のモデルを作成しなきゃいけないというデメリットがあります。
その他にも識別するためにCPUやGPUのリソースを大きく消費してしまうなどの点が上げられます

そのため今回では視覚障碍者向けのreCAPTCHA v2の救済措置の音声ベースのCaptchaをAIで解決していきます。
音声ベースのCaptchaはそもそもあることを知っている人も少ないでしょう
(音声ベースの画面に移動するにはCaptcha画面を開き、下部にあるヘッドフォンマークを押すことで開くことが出来ます。)

1, 基礎知識

画像識別より音声識別が優れている点として、まず音声識別は同じくGoogle社による無料の識別サービスが展開されています。
そして、その識別サービスは音声内の言葉を文字に文字起こしするもので、Captchaも若干識別がしづらいようにノイズが入った音声が提供されるのでこれを文字起こしして完了させることでほぼリソース消費0でreCAPTCHAを突破させるというものになります。

これによりWebクロールなどを行う必要がある事業では2Captchaなどを使用せずに済むため多少の負担軽減につながります。
その他にも軽くスクレイピングしたいときなどにもわざわざお金を払わずにサイトにアクセス、フォームの送信などを行うことが可能になります!

まず、解決の流れとして

  1. reCAPTCHAのiframeを探す

  2. 見つかればチェックボックスのiframeに移動

  3. チェックボックスを押す

  4. 表示されたiframeに移動

  5. 画面下部のヘッドフォンマークを押す

  6. 音声ダウンロードボタンにあるsrcの内容を取得

  7. 音声をバイナリーで変数に持つ

  8. pydubにてwav形式に変換

  9. SpeechRecognitionで文字起こしを行う

  10. 文字起こしした内容を送信

  11. reCAPTCHAのiframeにてaria-checkedの値がtrueになっていれば成功

以上が大雑把な流れとなっており今からこれを実装していきます。

2, 実装、実践

今回はPython環境でPlaywrightというSeleniumやPyppeteerのようなモジュールで一番扱いやすくクロスプラットフォーム的な感じで書かれているものを使用します。
Node.jsなどでもPlaywrightはリリースされておりほぼ同じような見た目で作られているので簡単に移植なども可能かと思われます。

また、PlaywrightはSeleniumのようにchromeの用意なども必要ではなく2個のコマンドを実行するだけで動くようになっています。
以下に今回使うモジュールの一覧をコピペで導入できるように記述します

pip install playwright
pip install requests
pip install pydub
pip install SpeechRecognition

以上を全て実行した後に以下を打つことでPlaywrightが導入され動くようになります。
(以下のコマンドを実行することでChromiumやWebDriverの導入が自動で行われます、有能)

playwright install-deps

まずはブラウザ操作時に簡単に次に進めるように音声の認識部分を簡単に使えるようにモジュール化していきます。
流れとしてはreCAPTCHAから取得してきた音声URLをrequestsモジュールでバイナリデータを取得してpydubにてAudioSegmentで色々音声を弄れるようにして、音声を整形して識別を行います。

def recognize(audio_url: str) -> str:
    seg = AudioSegment.from_file(io.BytesIO(requests.get(audio_url).content))
    seg = seg.set_channels(1) #音声ファイルをモノラルに
    seg = seg.set_frame_rate(16000) #識別には必要無いデータを削る
    seg = seg.set_sample_width(2) #同様
    audio = io.BytesIO()
    seg.export(audio, 'wav') #audioという変数にwavで出力(ファイルを作らない努力)
    audio.seek(0) #動画とか音楽のシークバーを一番最初に戻すイメージ
    r = sr.Recognizer()
    with sr.AudioFile(audio) as source:
        audio = r.record(source)
    return r.recognize_google(audio) #識別、結果が文字列で帰ってくる

この関数を用意しておくことで recognize("音声URL") を行うだけで自動で正しい形式に整えて文字起こしを行ってくれます。
これにはGoogleの音声認識を使っているため無料でリソースを消費せずに行うことが出来ます。

次にいきなり本番でreCAPTCHAが存在するサイトにアクセスして、チェックボックスを押し音声画面に移動し、解決させる一連の流れを書きましょう。
各1行ずつにコメントアウトでどのような処理をしているかが記載されています。

def main():
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=False, #開発用にheadlessをFalseに、これで視覚的にどのような操作を行っているかを確認できる
        )
        page = browser.new_page()
        page.goto('https://www.google.com/recaptcha/api2/demo') #reCAPTCHA v2のデモページ
        recaptcha = page.frame(name=page.locator('//iframe[@title="reCAPTCHA"]').get_attribute('name')) #reCAPTCHAのiframeを取得
        recaptcha.click('//span[@id="recaptcha-anchor"]') #reCAPTCHAのチェックボックスをクリック
        while recaptcha.locator('//span[@id="recaptcha-anchor"]').get_attribute('aria-disabled') == 'true': #reCAPTCHAのチェックボックスのローディングが終わるまで待機
            page.wait_for_timeout(1000) #1秒待機
        if recaptcha.locator('//span[@id="recaptcha-anchor"]').get_attribute('aria-checked') == 'true': #reCAPTCHAのチェックボックスがチェックされているか確認
            result = page.evaluate('grecaptcha.getResponse();') #reCAPTCHAが成功したので結果のトークンを取得
            browser.close() #ブラウザを閉じる
            return result #結果を返す
        verify = page.frame(name=page.locator('//iframe[starts-with(@src,"https://www.google.com/recaptcha/api2/bframe?")]').get_attribute('name')) #reCAPTCHAの認証ページのiframeを取得
        verify.click('//button[@id="recaptcha-audio-button"]') #reCAPTCHAの認証ページの音声認証ボタンをクリック
        audio = verify.locator('//a[@class="rc-audiochallenge-tdownload-link"]').get_attribute('href') #reCAPTCHAの認証ページの音声認証のURLを取得
        answer = recognize(audio) #音声認証のURLをrecognize関数に渡して文字列を取得
        verify.fill('//input[@id="audio-response"]', answer) #reCAPTCHAの認証ページの音声認証の入力欄に文字列を入力
        verify.click('//button[@id="recaptcha-verify-button"]') #reCAPTCHAの認証ページの音声認証の確認ボタンをクリック
        page.wait_for_timeout(1000) #1秒待機
        while recaptcha.locator('//span[@id="recaptcha-anchor"]').get_attribute('aria-disabled') == 'true': #reCAPTCHAのチェックボックスのローディングが終わるまで待機
            page.wait_for_timeout(1000) #1秒待機
        if recaptcha.locator('//span[@id="recaptcha-anchor"]').get_attribute('aria-checked') == 'true': #reCAPTCHAのチェックボックスがチェックされているか確認
            result = page.evaluate('grecaptcha.getResponse();') #reCAPTCHAが成功したので結果のトークンを取得
            browser.close() #ブラウザを閉じる
            return result #結果を返す
        #reCAPTCHAにチャレンジしたけど成功しなかった場合
        browser.close() #ブラウザを閉じる
        raise Exception('Failed to solve reCAPTCHA v2') #例外を発生させる

これが reCAPTCHA v2の公式デモページを自動で解決させるコードになります。
初心者がWebサイトを自動操作しようとすると目に見えるテキストなどから情報を取得してしまいがちですが目に見えるテキストは言語の違いなどで簡単に動かなくなってしまいます、確かに実装は簡単ですがこのコードのようにあまり変動しないidやclassから要素を取得してクリックなどを行うことをお勧めしています。

また、page.evaluate('grecaptcha.getResponse();') というコードですがこれは起動しているブラウザでgrecaptcha.getResponse();というJavaScriptを実行しています。
reCAPTCHAを解決した後はブラウザに解決した後のトークンを保持する必要がもちろんあります。そのためその保持しているトークンをこのコードを実行することで無理やり抜き出すことが出来ます。

以下に全てのコード、実行しただけで環境に問題が無ければ動作するコードを置きます。

from playwright.sync_api import sync_playwright
import requests
import speech_recognition as sr
from pydub import AudioSegment
import io

def recognize(audio_url: str) -> str:
    seg = AudioSegment.from_file(io.BytesIO(requests.get(audio_url).content))
    seg = seg.set_channels(1) #音声ファイルをモノラルに
    seg = seg.set_frame_rate(16000) #識別には必要無いデータを削る
    seg = seg.set_sample_width(2) #同様
    audio = io.BytesIO()
    seg.export(audio, 'wav') #audioという変数にwavで出力(ファイルを作らない努力)
    audio.seek(0) #動画とか音楽のシークバーを一番最初に戻すイメージ
    r = sr.Recognizer()
    with sr.AudioFile(audio) as source:
        audio = r.record(source)
    return r.recognize_google(audio) #識別、結果が文字列で帰ってくる

def main():
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=False, #開発用にheadlessをFalseに、これで視覚的にどのような操作を行っているかを確認できる
        )
        page = browser.new_page()
        page.goto('https://www.google.com/recaptcha/api2/demo') #reCAPTCHA v2のデモページ
        recaptcha = page.frame(name=page.locator('//iframe[@title="reCAPTCHA"]').get_attribute('name')) #reCAPTCHAのiframeを取得
        recaptcha.click('//span[@id="recaptcha-anchor"]') #reCAPTCHAのチェックボックスをクリック
        while recaptcha.locator('//span[@id="recaptcha-anchor"]').get_attribute('aria-disabled') == 'true': #reCAPTCHAのチェックボックスのローディングが終わるまで待機
            page.wait_for_timeout(1000) #1秒待機
        if recaptcha.locator('//span[@id="recaptcha-anchor"]').get_attribute('aria-checked') == 'true': #reCAPTCHAのチェックボックスがチェックされているか確認
            result = page.evaluate('grecaptcha.getResponse();') #reCAPTCHAが成功したので結果のトークンを取得
            browser.close() #ブラウザを閉じる
            return result #結果を返す
        verify = page.frame(name=page.locator('//iframe[starts-with(@src,"https://www.google.com/recaptcha/api2/bframe?")]').get_attribute('name')) #reCAPTCHAの認証ページのiframeを取得
        verify.click('//button[@id="recaptcha-audio-button"]') #reCAPTCHAの認証ページの音声認証ボタンをクリック
        audio = verify.locator('//a[@class="rc-audiochallenge-tdownload-link"]').get_attribute('href') #reCAPTCHAの認証ページの音声認証のURLを取得
        answer = recognize(audio) #音声認証のURLをrecognize関数に渡して文字列を取得
        verify.fill('//input[@id="audio-response"]', answer) #reCAPTCHAの認証ページの音声認証の入力欄に文字列を入力
        verify.click('//button[@id="recaptcha-verify-button"]') #reCAPTCHAの認証ページの音声認証の確認ボタンをクリック
        page.wait_for_timeout(1000) #1秒待機
        while recaptcha.locator('//span[@id="recaptcha-anchor"]').get_attribute('aria-disabled') == 'true': #reCAPTCHAのチェックボックスのローディングが終わるまで待機
            page.wait_for_timeout(1000) #1秒待機
        if recaptcha.locator('//span[@id="recaptcha-anchor"]').get_attribute('aria-checked') == 'true': #reCAPTCHAのチェックボックスがチェックされているか確認
            result = page.evaluate('grecaptcha.getResponse();') #reCAPTCHAが成功したので結果のトークンを取得
            browser.close() #ブラウザを閉じる
            return result #結果を返す
        #reCAPTCHAにチャレンジしたけど成功しなかった場合
        browser.close() #ブラウザを閉じる
        raise Exception('Failed to solve reCAPTCHA v2') #例外を発生させる

if __name__ == '__main__':
    print(main())

たったこれだけのコードでお金をかけずにreCAPTCHAを無害化することが出来ます。
またreCAPTCHAは簡単に作られておりheadlessモードでもほぼ正常に動作するためとても簡単な部類と言えます
(headlessでは稀に動かない場合もある、例外あり)

このコードを参考にアクセスしたいページなどを分けて自分なりにコードを書けば簡単にスクレイピングやフォームの送信を行えます。

3, まとめ

最後まで読んでいただきありがとうございます。
音声にも多少のデメリットがありGoogleもこの問題を認知しているため、画像識別よりも時間当たりの制限が厳しく設定されています。
普段使いする分には大丈夫ですが大量に解決するなどは難しいと思われます。

またこのコードは簡易的なものであり一切の検知対策を行っておらずCloudflareなどが設定されている場合はこのコードでは動きません
ブラウザが自動操作されていることがバレバレなので

以下に有料ゾーンとして検知を完全に回避してサイト運営者、Google、Cloudflareなどに一切バレずにreCAPTCHAを解決するための関数を書きました
以下の関数を使用することで自分なりにコードをカスタマイズせずに solve(page) などを行うだけで自動でreCAPTCHAを解決してくれる、一切手間がかからず簡単に使えるものを用意しましたのでぜひこの記事に価値を見出した方やコードが欲しい方は購入よろしくお願いします。

Cloudflareのこの画面に引っかかってる人もおまけで回避方法を書いてありますのでそういったものも求めている方もおすすめです!

最後に、個人的に連絡いただければ2CaptchaのようにAPIにリクエストするだけでTokenを返すAPIを2Captchaなどよりも安価に提供、APIを建てているソースコードなどを丸々販売可能ですので個人的な取引をしたい方は以下のメールアドレス宛に連絡をください
reCAPTCHA以外にもhCaptchaなどはさらに品質の高い物を容易出来ます。kohnoselami@gmail.com

Extra, 検知回避、完全版ソースコード

ここから先は

2,793字

¥ 10,000

この記事が気に入ったらチップで応援してみませんか?