プロキシの請求が収集したデータの量よりも早く増加している場合、問題はほとんど常にリトライロジックにあります。 パーサーは失敗したリクエストを何度も黙って再試行し、タイムアウトやキャプチャにトラフィックを消費しますが、 開発者はログにこれらの費用を見ることすらありません。実際の損失を計算し、データの質を損なうことなくそれを削減する方法を見ていきましょう。
なぜリトライロジックがトラフィックを消費するのか
ほとんどのパーサーは単純なリトライロジックで書かれています:リクエストが失敗した場合は再試行し、3〜5回まで繰り返します。 問題は、各再試行リクエストが新しいHTTPリクエストだけでなく、完全なサイクルを含むことです:TCPハンドシェイク、 TLSネゴシエーション、ページ全体の読み込み(必要なのはデータのブロックだけでも)、場合によっては ヘッドレスブラウザを使用している場合、画像やJSファイルの再読み込みも行われます。
特に高価な再試行は、レジデンシャルプロキシを介して作業する際に発生します。 ここでは、トラフィックはリクエストの数ではなく、量に基づいて課金されます。商品ページの失敗したリクエスト 1回が300〜500KBかかることがあります。パーサーがタイムアウト時に3回再試行を行うと、 同じ失敗したリクエストに対して4回連続で支払うことになります — そして、4回目の試行が成功する保証はありません。
もう一つの理由は、再試行で修正できないエラーによるものです。サイトがボットを検出して403を返した場合、 同じフィンガープリントと同じセッションのクッキーで再試行リクエストを送信すると、ほぼ確実に 同じ応答が返ってきます。パーサーは、IPアドレスまたはブラウザのフィンガープリントが変更されるまで 成功する可能性のない試行にトラフィックを消費します。
実際にどれだけのトラフィックが再試行に消費されるのか
問題の規模を理解するために、簡単な例を考えましょう。パーサーはマーケットプレイスから商品カードを収集し、 平均応答サイズは250KB(HTML + JSON API + 部分的な静的ファイル)です。ブロックなしで安定して動作している場合、 失敗したリクエストの割合は5〜8%の範囲に留まります。しかし、安価なデータセンターのプロキシを介して 積極的にパースすると、この割合は25〜35%に上昇する可能性があります。なぜなら、ターゲットプロバイダーは パターンをすぐに認識し、キャプチャやIPによる一時的な禁止を返し始めるからです。
数字で計算してみましょう。100,000の商品カードを収集する必要があるとしましょう:
| 失敗したリクエストの割合 | 1リクエストあたりの再試行数(平均) | 最終的なトラフィック | 過剰消費 |
|---|---|---|---|
| 5% | 0.15 | 28.75 GB | +15% |
| 15% | 0.45 | 36.25 GB | +45% |
| 30% | 0.90 | 47.5 GB | +90% |
見ての通り、失敗率が30%で「3回まで再試行する」リトライ戦略を採用すると、実際のトラフィックは 理論的な最小値の25GBに対してほぼ倍増します。これらの余分な20GB以上は、プロキシに対する予算の 直接的な損失であり、再試行のロジックを見直すことで削減可能です。
パーサーのリトライロジックにおける典型的な誤り
リトライロジックを修正する前に、90%の自作パーサーで見られる典型的なアンチパターンを認識することが重要です:
- エラーコードを考慮しないリトライ。 403、429、500、タイムアウト、接続の切断など、あらゆる異常事態で再試行が行われますが、 それぞれの処理戦略は異なるべきです。
- 再試行間の固定遅延。 例えば、最初の試行であろうと5回目であろうと、試行間に2秒の遅延を設けることは、サイトにとっては攻撃的すぎるか、 大量のリクエストには遅すぎます。
- 同じIPと同じセッションでの再試行。 サイトがフィンガープリントでリクエストをブロックした場合、同一のパラメータで再試行しても結果は変わらず、 トラフィックを消費します。
- 試行回数に上限がない。 一部のパーサーは「死んだ」URLに執着し、諦める前に何十回も再試行を行います。
- 一時的エラーと永続的エラーの区別がない。 404(ページが存在しない)と503(サーバーが一時的に利用できない)は異なるロジックを必要としますが、 しばしば同じように処理されます。
Pythonコードによる指数バックオフ
簡単ですが効果的な解決策は、ジッター(ランダムなばらつき)を伴う指数的な遅延です。これにより無意味な再試行の数が減り、 時間的に負荷が分散されます。試行間の固定の待機時間の代わりに、遅延が指数的に増加し、サイトがブロック後に「冷却」する時間を与え、 パーサーがほぼ確実に失敗するリクエストにトラフィックを消費しないようにします。
import time
import random
import requests
def fetch_with_backoff(url, proxies, max_retries=4, base_delay=1.0):
retryable_codes = {429, 500, 502, 503, 504}
non_retryable_codes = {404, 410}
for attempt in range(max_retries + 1):
try:
response = requests.get(url, proxies=proxies, timeout=10)
if response.status_code == 200:
return response
if response.status_code in non_retryable_codes:
# 再試行する意味がない — ページは物理的に存在しない
return None
if response.status_code not in retryable_codes:
return None
except (requests.exceptions.Timeout,
requests.exceptions.ConnectionError):
pass # 一時的なネットワークエラー — 再試行可能
if attempt == max_retries:
return None
# ジッターを伴う指数的遅延
delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
time.sleep(delay)
return None
このコードの重要なアイデアは、エラーを3つのカテゴリに分けることです:再試行で修正できないもの(404、410)、 遅延を伴って再試行できるもの(429、500-504、タイムアウト)、および追加の試行に費用をかけずにすぐに失敗と見なされるもの。 このような分け方だけで、単純な「すべてを再試行する」よりも20〜30%余分なトラフィックを削減できます。
アドバイス: Retry-After ヘッダーを処理に追加してください —
多くのサイトは再試行前に待機すべき秒数を自ら示しています。このヘッダーを無視することは、
不必要な禁止やトラフィックの一般的な原因です。
サーキットブレーカー:いつ停止すべきか
指数バックオフは1つのリクエストのレベルで役立ちますが、ドメイン全体や特定のプロキシノードが 数百のURLに対して一時的に利用できない状況からは保護されません。ここで必要なのがサーキットブレーカーのパターンです — 「自動スイッチ」で、最近のエラー率を追跡し、しきい値を超えた場合に試行を一時的に停止します。 閉じたドアを叩き続けるのではなく、試行を停止します。
class CircuitBreaker:
def __init__(self, failure_threshold=0.5, window_size=50, cooldown=60):
self.failure_threshold = failure_threshold
self.window_size = window_size
self.cooldown = cooldown
self.results = []
self.open_until = 0
def is_open(self):
return time.time() < self.open_until
def record(self, success: bool):
self.results.append(success)
if len(self.results) > self.window_size:
self.results.pop(0)
if len(self.results) == self.window_size:
failure_rate = 1 - sum(self.results) / self.window_size
if failure_rate > self.failure_threshold:
self.open_until = time.time() + self.cooldown
self.results.clear()
ロジックは簡単です:最近の50回のリクエストのうち半数以上が失敗した場合、パーサーはこのドメインまたはプロキシに対する 試行を60秒間停止します。この間にIPを変更したり、リクエストの速度を下げたり、別のプロキシプールに切り替えたりできます。 これは、リクエスト頻度を超えた場合にIP範囲を一時的に禁止するターゲットサイトで作業する際に特に重要です — 閉じたドアを叩き続けることは、単に無駄にトラフィックを消費することを意味します。
再試行時のスマートプロキシローテーション
不必要なリトライに対する最も効果的な対策の1つは、すでに拒否されたIPからリクエストを再試行しないことです。 ロジックは簡単です:エラーがIPによるブロックに関連している場合(403、429、キャプチャへのリダイレクト)、 再試行前にプロキシを変更することで成功の可能性が大幅に向上し、試行回数が減少します。
Wildberries、Ozon、Avitoなどのマーケットプレイスをパースする場合、通常のリクエストは データセンターのプロキシを介して行われます — これらはより速く、安価です。 そして、ブロック検出器が数回連続して作動した場合、パーサーは レジデンシャルプロキシに切り替えます。 これらはアンチボットシステムのフィルターにかかることが少ないです。このハイブリッドアプローチは、全体のトラフィック消費を減少させます。 高価なレジデンシャルIPは、実際に必要な場合にのみ使用され、すべてのリクエストに対して使用されるわけではありません。
| エラータイプ | 再試行戦略 | IPの変更が必要か? |
|---|---|---|
| 接続のタイムアウト | バックオフ、1-2回の再試行 | いいえ |
| 403 / キャプチャ | 即時ローテーション | はい、必須 |
| 429(レート制限) | Retry-Afterによるバックオフ | 望ましい |
| 500-503 | バックオフ、2-3回の再試行 | いいえ |
| 404 / 410 | 再試行なし | — |
モバイルトラフィックをエミュレートするパーサー(例えば、マーケットプレイスやソーシャルメディアのアプリのモバイルバージョンからデータを収集する場合)には、 モバイルプロキシを使用することが理にかなっています — これらは通信事業者が同じIPを同時に何千人もの実際のユーザーに配布するため、保護システムに対して疑念を抱かれることが少なく、 特定のブロックがサイトのターゲットに対して非効率的になります。
リトライメトリクスのモニタリング
メトリクスがなければ、リトライロジックの最適化は推測に過ぎません。各リクエストでログに記録すべき最小限の指標は以下の通りです:
- リトライ率 — 1回以上の再試行を必要としたリクエストの割合。
- リトライ後の成功率 — どのくらいの割合の再試行が最終的に成功したか(この指標が低い場合、再試行は単にトラフィックを消費するだけです)。
- 成功した結果のトラフィック — 送信されたデータの総量を成功したレコードの数で割ったもの。これは効果的なメトリクスの鍵です。
- エラーコードによるエラーの分布 — どこで主なトラフィック漏れが発生しているかを理解するのに役立ちます:タイムアウト、403、429、またはその他。
- 特定のプロキシノードによるリトライ率 — 1つのIPがリトライ率80%を示し、他のIPが10%の場合、問題はローカルであり、特定のノードを変更することで解決できます。
Google Sheetsの簡単な表や、これら5つのメトリクスを1時間ごとに更新するCSVログでも、異常を確認し、戦略を適時修正するために十分なデータが得られます — 例えば、特定のサイトセクションへのリクエスト頻度を下げたり、プール内のレジデンシャルIPの割合を増やしたりすることができます。
リトライによるトラフィック最適化のチェックリスト
- エラーコードをリトライ可能と非リトライ可能に分ける — 404/410は再試行しない。
- 固定の遅延の代わりにジッターを伴う指数バックオフを実装する。
- サイトが提供する場合は、
Retry-Afterヘッダーを尊重する。 - 403やボット検出の疑いがある場合は再試行前にIPを変更する。
- 試行回数に厳しい制限を設ける(通常3〜4回で十分)。
- 高い失敗率のドメインやプロキシノードに対してサーキットブレーカーを実装する。
- リトライ率と成功した結果のトラフィックをログに記録する — メトリクスがなければ最適化は不可能。
- プロキシプールを分ける:安定したセクションには安価なデータセンターを、問題のあるセクションにはレジデンシャルまたはモバイルIPを使用する。
結論
リトライロジックはパーサーの小さな詳細ではなく、データ収集のコストに影響を与える主要な要因の1つです。 単純な「すべてを再試行する」戦略は、理論的な最小値に対して実際のトラフィックを40〜90%増加させる可能性があり、 その大部分の再試行は最初の試行と同じ失敗で終わります。エラーをタイプ別に分け、指数バックオフ、サーキットブレーカー、 スマートIPローテーションを使用することで、データ収集の完全性を損なうことなく、これらの損失を大幅に削減できます。
あなたのパーサーがボットを積極的に検出するサイト(マーケットプレイス、ソーシャルメディア、広告プラットフォーム)で動作する場合、 タスクに応じて複数のタイプのプロキシを組み合わせることが重要です。基本的な操作にはデータセンターのプロキシが適しており、 最大のブロック耐性が必要な場合には、レジデンシャルプロキシを使用します。 このようなハイブリッドアプローチは、同じデータ収集量でトラフィック消費を顕著に削減します。