Seleniumは最も広く使われているブラウザ自動化ツールであり、JavaScriptの多いターゲットのスクレイピングにおいてもその役目を果たす。しかし長年にわたる一つのギャップがあり、多くの人がresidential proxyを初めて追加する際につまずく。プロキシホストの設定は簡単だが、ユーザー名とパスワードの提供はそうではない。なぜならSeleniumにはそれを行う組み込みの方法がないからだ。認証が必要なプロキシにChromeを向けると、ネイティブの407ログインダイアログがポップアップし、Seleniumはそれを埋めることができず、スクリプトはハングする。以下では、それを回避する3つの方法を説明する。
これは他のブラウザガイド、Playwrightでのresidential proxyの使用やPuppeteerでの使用と並ぶ内容であり、これら両方はプロキシ認証をネイティブに処理する。フルブラウザが不要であれば、シンプルなHTTPクライアントを使ったPythonでのプロキシの方がさらに簡単だ。
以下すべての内容はShifterのレジデンシャルゲートウェイを使用する。エンドポイントは一つ、p.shifter.io:443で、すべてのターゲティングはユーザー名にエンコードされる。ホストと認証情報を別のプロバイダーのものに置き換えても、構造は同じだ。
ゲートウェイモデルを一段落で
プロキシのユーザー名は認証情報とターゲティング情報の両方を運ぶ。国やセッションを変更するのにエンドポイントを切り替えるのではなく、ユーザー名の文字列を変更する。
customer-USERNAME-country-us-sid-abc123-ttl-600country-usはアメリカ合衆国をターゲットにし、sidはスティッキーセッションを固定し、ttlはそのIPをN秒間保持する。sid/ttlを省略すると、新しい接続ごとにローテーションする。パスワードは常に一定のままだ。Seleniumにおける問題全体は、このユーザー名とパスワードをプロキシに渡すことにある。
核心的な問題
ホストの設定は簡単な方の半分だ。Chromeのオプションで--proxy-serverを渡す方法は他の場合とまったく同じだ。
from selenium import webdriver
options = webdriver.ChromeOptions()options.add_argument('--proxy-server=http://p.shifter.io:443') # host onlydriver = webdriver.Chrome(options=options)これはIPホワイトリスト方式で認証情報のないプロキシに対しては機能する。しかしこのゲートウェイはユーザー名とパスワードによる認証が必要であり、Chromiumはそのフラグから認証情報を読み込まないため、最初のナビゲーションはSeleniumが解除できない407認証プロンプトで停止する。この課題に答えるには、3つの方法のうちいずれかが必要だ。
アプローチ1: Selenium Wire(簡単な方法)
Selenium Wireは Seleniumを拡張し、プロキシの認証情報を直接受け付け、認証をあなたの代わりに処理する。これは最も摩擦の少ない選択肢であり、多くのPythonスクレイパーが採用する方法だ。
from seleniumwire import webdriver # pip install selenium-wireimport os
user = os.environ['SHIFTER_USER'] + '-country-us' # targeting in the usernamepw = os.environ['SHIFTER_PASS']
seleniumwire_options = { 'proxy': { 'http': f'http://{user}:{pw}@p.shifter.io:443', 'https': f'http://{user}:{pw}@p.shifter.io:443', 'no_proxy': 'localhost,127.0.0.1', }}
driver = webdriver.Chrome(seleniumwire_options=seleniumwire_options)driver.get('https://api.ipify.org')print(driver.page_source) # a US residential IPdriver.quit()認証情報は、ユーザー名内のターゲティングフラグを含めて、proxy設定に入れられ、Selenium Wireが407を透過的に処理する。また実行時にdriver.proxyを再割り当てすることでプロキシを変更でき、Chromeを再起動せずにローテーションを行うのに便利だ。
アプローチ2: 認証情報用の拡張機能(バニラSelenium、追加ライブラリなし)
プレーンなSeleniumのままにしたい場合、古典的な手法は、認証チャレンジにあなたの認証情報で応答する小さなChrome拡張機能を読み込むことだ。これをその場で構築し、add_extensionで渡す。
# manifest.json declares proxy + auth permissions; background.js supplies creds.background_js = """chrome.webRequest.onAuthRequired.addListener( () => ({ authCredentials: { username: USER, password: PASS } }), { urls: ['<all_urls>'] }, ['blocking']);""".replace('USER', repr(user)).replace('PASS', repr(pw))# zip manifest.json + background.js, then:options.add_extension('proxy_auth.zip')これによって依存関係を持たずに済み、拡張機能が処理を行うため、どのSeleniumの言語バインディングでも機能する。注意点としては、上記のブロッキングonAuthRequiredパターンはManifest V2の手法であり、ChromeはMV2を段階的に廃止しMV3へ移行しているため、現行のChromeではこのアプローチは以前よりも脆弱になっている。新規に始めるなら、Selenium Wireか下記のCDP方式を推奨する。
アプローチ3: Selenium 4でのCDP
Selenium 4はChrome DevTools Protocolを公開しており、Fetchドメインを通じてFetch.authRequiredを処理し、認証情報とともにリクエストを継続することでプロキシ認証に応答できる。これは現代のSeleniumにネイティブであり、追加パッケージを必要としないが、手動で配線するのは面倒で、本質的にはSelenium Wireがすでにラップしている処理を再実装することになる。サードパーティの依存関係をゼロにしたく、CDPに慣れているなら選ぶとよい。そうでなければSelenium Wireの方が手間を省ける。
地域とセッションのローテーション
ターゲティング情報がユーザー名に含まれているため、異なるアイデンティティは異なるユーザー名を意味し、Seleniumではプロキシはブラウザレベルで設定される。つまりローテーションはタブ単位ではなくドライバ単位で発生する。実用的なパターンは二つある。Selenium Wireでは、実行時にdriver.proxyを再割り当てして作業単位間でユーザー名を切り替える。拡張機能やCDPアプローチでは、アイデンティティごとにドライバを実行しプールする。いずれの方法でも、論理的な作業単位ごとに独自のsidを与え、セッションの途中ではなく単位間でローテーションする(スティッキーとローテーティングの違いで扱っている区別)。そして負荷分散に関する投稿が説明するように、作業をアイデンティティにマッピングする。
ドライバを再利用し、並行性に上限を設ける
Chromeの起動にはコストがかかる。リクエストごとに新しいドライバを起動すると、毎回実際の起動時間とメモリのコストがかかり、これはレイテンシガイドがなくそうとしているオーバーヘッドだ。ドライバ(または小規模なプール)を起動し、リクエスト間で再利用すること。そして各ドライバは実際のメモリを保持する完全なブラウザであるため、何千も実行することはできない。プールに上限を設け、ターゲットホストごとの実行中作業を制限し、脆弱な一つのサイトが叩かれる一方で寛容なサイトが飢餓状態にならないようにすること。ターゲットの許容範囲を超えた並列性は、スループットではなくブロックやメモリ不足のクラッシュを招く(ブロックを回避する方法)。
ブロックされないためにはブラウザだけでは半分にすぎない
レジデンシャルIPは人間らしく見せるためのネットワーク面の半分を処理するが、Seleniumは依然として自動化されたブラウザを操作しており、サイトはそれもフィンガープリントする。navigator.webdriver、自動化フラグ、ヘッドレス特有の癖などだ。良好なレピュテーションを持つクリーンなIPは多くのチャレンジを回避できるが、明らかに自動化されたブラウザを偽装することはできない。ユーザーエージェントとビューポートを現実的に保ち、ページを人間らしいペースで操作し、検出を引き起こす一般的な誤りがIP層と同じくらいブラウザ層にも当てはまることを忘れないこと。両者は一致していなければならない。
実際にプロキシ経由になっているか確認する
ベンチマークや他のデバッグを行う前に、ブラウザ内から実際の出口IPを確認する。
driver.get('http://ip-api.com/json')print(driver.find_element('tag name', 'body').text) # expect the targeted country自分自身のIPが表示される場合はプロキシが適用されていない。ログインダイアログでのハングは、認証ステップ(Selenium Wire、拡張機能、CDP)が欠けているか設定ミスであることを意味する。一般的なハングはローカルの発信が遮断されていることを意味する。この3つはすべてタイムアウト診断ガイドで扱われている。
よくある質問
なぜSeleniumはプロキシのログインポップアップでハングするのか?
Chromiumは認証が必要なプロキシに対してネイティブの407認証ダイアログを出すが、Seleniumはネイティブブラウザダイアログと対話できない。別の方法でチャレンジに応答する必要がある。Selenium Wire、認証情報用の拡張機能、あるいはCDPのFetch.authRequiredだ。--proxy-serverだけを設定してもホストが提供されるだけで、認証情報は提供されない。
--proxy-serverにuser:pass@hostを入れられるか?
できない。Chromiumは--proxy-serverフラグから認証情報を読み込まない。ホストはそこで指定し、ユーザー名とパスワードは上記3つの方法のいずれかで提供すること。ゲートウェイはターゲティング情報をユーザー名にエンコードするため、(-country-...を含む)完全なユーザー名がプロキシのユーザー名として渡すものになる。
Selenium Wireを使わなければならないか? いいえ、しかしPythonでは最もシンプルな方法だ。依存関係のない代替手段は、認証情報用の拡張機能(どの言語バインディングでも機能するが、古典的なMV2パターンは段階的に廃止されつつある)や、Selenium 4でのCDP(ネイティブだが配線に手間がかかる)だ。
SeleniumでIPをローテーションするにはどうすればよいか?
プロキシのユーザー名を変えることで、同じゲートウェイを通じてアイデンティティが変わる。Selenium Wireでは実行時にdriver.proxyを再割り当てできる。それ以外の場合はアイデンティティごとに一つのドライバを実行し、プールする。ユーザー名内のsidを省略すると、新しい接続のたびにローテーションする。
Selenium、Playwright、Puppeteerのどれを選ぶべきか? PlaywrightとPuppeteerはどちらもプロキシの認証情報をネイティブに受け付けるため、この一連の手間全体を回避できる。Seleniumはここで説明した回避策のいずれかを必要とする。すでにスタックで使われているなら、Seleniumは依然として良い選択肢だ。新規に始めて手間のないプロキシ認証を望むなら、他の二つの方がスムーズだ。
結論
Seleniumとレジデンシャルプロキシの組み合わせは、Seleniumが持つ一つのギャップを解決すれば十分に機能する。--proxy-serverでプロキシホストは受け付けるが、認証が必要なプロキシの認証情報を提供するには助けが必要だ。摩擦を最小にするならSelenium Wireを、依存関係を持ちたくないなら認証情報用の拡張機能を、ネイティブな方法を望むならSelenium 4でのCDPを使うこと。そしてユーザー名を変えることで地域とセッションをローテーションし、長寿命のドライバを再利用し、各ドライバが実際のブラウザであるため並行性に上限を設け、ブラウザのフィンガープリントをIPと同じくらい人間らしく保つこと。
これを正しく行えば、Seleniumはプレーンな HTTPクライアントでは扱えない、インタラクティブでJavaScriptの多いターゲットを処理できる。レジデンシャルゲートウェイにそれを向け、プールの品質がそもそもどれだけ頻繁にチャレンジされるかを左右することを覚えておくこと(IPレピュテーション)。料金ページには、あなた自身のターゲットに対して試すためのGB単位のプランが掲載されている。