プロキシローテーションは、解決策ではなく、一時しのぎに過ぎません。スクレイピングの大半が失敗するのは、IPアドレスだけがアンチボットシステムの測定対象であると誤認しているからです。実際には、Akamai Bot Manager、Cloudflare Turnstile、Datadomeといった最新のボットマネージャーは、送信元IPよりもはるかに多くの情報をフィンガープリントとして取得しています。無料の公開プロキシをローテーションで使っても、これらのシステムに対してはほとんど効果がなく、むしろ状況を悪化させることの方が多いのです。
IPローテーションの幻想
リクエストごとにIPをローテーションすると、自分がスクレイパーであることを宣言しているようなものです。人間のブラウジングパターンはスティッキーセッションを示します。つまり、数分から数時間にわたって同一IPを使い、ブラウザのフィンガープリントは一貫し、リクエスト間隔も予測可能です。requestsとSessionオブジェクト、そしてローテーションするプロキシリストを組み合わせたツールは、これらのシグナルをすべて破壊します。AkamaiのX-Akamai-Device-FingerprintヘッダーやCloudflareのcf-request-id相関は、TLSパラメータやHTTP/2設定、タイミングが同一である場合、異なるIPからのリクエストを関連付けることができます。DatadomeのJavaScriptチャレンジは、プロキシ変更後も残るヘッドレスブラウザの痕跡をチェックします。クライアントの完全なフィンガープリントをローテーションせずにIPだけを変えるのは、ナンバープレートを変えても同じ車を運転しているようなもので、料金所のカメラには依然として捕捉されます。
低レート・低ボリュームのスクレイピングで、基本的なIPベースのレート制限(例えば、JavaScriptチャレンジなしで1分間に10リクエストのスロットル)しか使っていないサイトを対象とする場合、多くの場合、1つの住宅用IPで十分です。私は長年にわたり、政府のデータポータルや公開APIに対して、1つの静的IPと丁寧なtime.sleep(2)を使ってスクレイパーを運用してきました。プロキシは不要です。ルールは単純です。50リクエスト後にチャレンジページやCAPTCHAが表示されないサイトであれば、ローテーションは必要ありません。
IPアドレスを超えて:フィンガープリンティング
アンチボットシステムは現在、リクエストごとに数十のシグナルを収集しています。User-Agent文字列は簡単に偽装できますが、Accept-Language、Sec-CH-UA、Connection、Accept-Encodingの順序はそうではありません。さらに重要なのは、JA3ハッシュ(JA3参照)で標準化されたTLSフィンガープリンティングです。これは、暗号スイートの順序とTLS拡張リストによってクライアントライブラリを識別します。Pythonのrequestsライブラリ(urllib3経由)が生成するJA3ハッシュは、Chrome 124のものとは異なります。CloudflareのTurnstileとDatadomeはどちらもJA3をチェックします。同じTLSスタックを維持したままIPをローテーションすると、すべてのリクエストが同じ自動化クライアントのように見え、単に出口ノードを切り替えているだけになります。無料プロキシは、古いOpenSSLバージョンを実行していたり、すでにブラックリストに載っているボット的なTLS構成を使用していることが多いため、この問題をさらに悪化させます。
HTTP/2フィンガープリンティングはさらに進んでいます。SETTINGSフレーム、ウィンドウ更新値、ストリーム同時実行パラメータは、AkamaiのBot Managerがセッション間で追跡する独自の「HTTP/2フィンガープリント」を形成します。HTTP/2実装をローテーションしないプロキシプールは、簡単にクラスタリングされてしまいます。これらのチェックを回避する唯一の方法は、実際のブラウザエンジン(Puppeteer、Playwright)を使用するか、特定のブラウザバージョンを模倣するように注意深く作られたTLS/HTTPスタックを使用することです。その場合でも、特定のセッションからのリクエスト全体で同じフィンガープリントを維持する必要があります。
無料公開プロキシプールの経済性
私のテストでは、無料の公開プロキシリストの60~80%は失敗します。ほとんどのプロキシは、最初から使えなかったり、ホスト側でスロットルがかけられていたり、主要なボットマネージャーにすでにフラグが立てられています。公開ディレクトリからスクレイピングした無料SOCKS5プロキシの平均寿命は15分未満です。500のプロキシをローテーションするプールを維持するということは、1時間に数千のIPを消費し、リクエストの80%がタイムアウトするか403を返すことを意味します。帯域幅は信頼性が低く、レイテンシのスパイクは頻繁に発生し、多くの無料プロキシは広告を挿入したり、レスポンスボディを改変したりします。有料の住宅用プロキシネットワーク(例:Bright Data、Oxylabs)は95%以上の成功率とスティッキーセッションオプションを提供しますが、コストは1GBあたり10~20ドルです。規模の面では、住宅用プロキシが有利になるのは、高価値のターゲットに対するIPベースのブロックを回避する必要がある場合のみです。それ以外の場合は、適切なリクエストペーシングを備えた単一のクリーンなIPの方が、混沌とした無料プールよりも優れたパフォーマンスを発揮します。
ローテーションが実際に機能する場合
プロキシローテーションが効果的なのは、特定の脅威、すなわちIPごとにリセットされるIPベースのレート制限に対してのみです。サイトが単純なX-Forwarded-ForチェックやIPごとのトークンバケットを使用している場合、リクエストごとにローテーションすることで制限を回避できます。これは、ボット検出を更新していない小規模なEコマースサイトやレガシーAPIでよく見られます。そのような場合、無料のプロキシプールでも機能します。ただし、失敗したプロキシを破棄し、新しいプロキシを迅速に循環させるリトライロジックを実装することが条件です。
以下は、requestsとリトライ付きローテーションループを使用した最小限のPythonの例です。proxy_listにプロキシURLのリスト、ターゲットのurlがあることを前提としています。
import requests
from itertools import cycle
proxy_pool = cycle(proxy_list)
max_retries = 5
for attempt in range(max_retries):
proxy = next(proxy_pool)
try:
resp = requests.get(
url,
proxies={"http": proxy, "https": proxy},
timeout=10,
headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..."}
)
if resp.status_code == 200:
break
except (requests.ConnectionError, requests.Timeout):
continue
else:
raise RuntimeError("All proxies failed")
このパターンが機能するのは、サイトの検出が純粋にIPベースである場合のみです。リクエスト間にtime.sleep(random.uniform(1,3))を追加して、人間のタイミングを模倣してください。TurnstileやDatadomeを実行しているサイトの場合、このコードは毎回失敗します。プロキシに関係なく、チャレンジページが403またはCAPTCHAを返すからです。そのような場合、必要なのはローテーションするIPリストではなく、実際のフィンガープリントを持つヘッドレスブラウザです。
スティッキーセッション(関連する一連のリクエストに対して同じIPを維持すること)は、リクエストごとのローテーションよりも効果的な場合がよくあります。多くのEコマースサイトでは、ブラウジングセッション(例:カートに商品を追加、チェックアウト)で単一のIPを期待しています。セッション途中でのローテーションは不正フラグを引き起こします。プロキシプールを使用するが、リクエストごとではなくセッションごとに1つのIPを割り当ててください。無料プロキシは、同じIPが複数のユーザーによって再利用されるため、スティッキーセッションをサポートすることはほとんどありません。セッションデータの相互汚染が発生します。有料の住宅用プロキシは、自然なブラウジング行動に合ったスティッキーセッション期間(5~30分)を提供します。
ローテーションは、ターゲットの検出スタックを理解している場合にのみ選択してください。まずは単一のIPでテストしてください。レート制限に達した場合にのみローテーションを追加してください。そして、本番環境で無料プロキシに依存しないでください。その失敗率は、安価な住宅用プロキシプランよりも、エンジニアリング時間とデータ損失の面で大きなコストをもたらします。