1. 結論(この記事で得られること)
この記事では、スクレイピング処理におけるリトライ戦略の実装パターンを、実務で即使えるレベルで解説します。
得られるもの:
- Exponential Backoffを含むリトライパターンの実装コード
- レート制限、タイムアウト、一時エラーの見極め方
- AI(Claude/GPT)を使った障害原因の最速特定法
- テスト・監視戦略とインシデント対応の型
僕も昔、リトライなしのスクレイピングバッチを書いて、深夜3時にアラートで起こされた経験があります。あの時ちゃんとエラーハンドリング設計していれば…という後悔から、このノウハウをまとめました。
2. 前提(環境・読者層)
想定読者:
- Python でスクレイピング経験あり(requests / BeautifulSoup / Selenium など)
- リトライ処理を「とりあえず3回ループ」で書いて後悔したことがある
- 本番運用での障害対応・監視設計に興味がある
動作環境:
- Python 3.9+
- requests, tenacity(リトライライブラリ)
- Playwright または Selenium(動的サイト用)
3. Before:よくあるつまずきポイント
3-1. ナイーブなリトライ実装
# ❌ よく見る危険なパターン
def scrape_page(url):
for i in range(3):
try:
response = requests.get(url, timeout=10)
return response.text
except:
if i == 2:
raise
time.sleep(1)
問題点:
- すべての例外を無差別にリトライ(404もリトライしてしまう)
- 固定間隔sleep → 相手サーバーに負荷をかけ続ける
- リトライ失敗時のログが残らない
- タイムアウトと接続エラーの区別がない
3-2. 実際に起こる現場の問題
僕が過去に遭遇したケース:
- Case1: CloudflareのDDoS保護でブロックされたのにリトライし続け、IP BAN
- Case2: 503エラー(メンテナンス)を30分リトライして無駄にリソース消費
- Case3: レート制限(429)の Retry-After ヘッダーを無視して即座にリトライ
これらはすべて「リトライすべきエラー」と「即座に諦めるべきエラー」の区別がついていなかったことが原因です。
4. After:基本的な解決パターン
4-1. エラー分類マトリクス
429 (Rate Limit)
リトライ: ✅
戦略: Retry-Afterヘッダーに従う
500, 502, 503
リトライ: ✅
戦略: Exponential Backoff
504 (Timeout)
リトライ: ✅
戦略: タイムアウト値を増やして再試行
404, 403, 401
リトライ: ❌
戦略: 即座に失敗扱い(ログのみ)
ConnectionError
リトライ: ✅
戦略: ネットワーク起因、短時間で回復可能性
4-2. 改善版:基本実装
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import time
def create_session_with_retry():
"""リトライ機能付きセッション生成"""
session = requests.Session()
# リトライ対象のステータスコード
retry_strategy = Retry(
total=3,
status_forcelist=[429, 500, 502, 503, 504],
backoff_factor=1, # 1秒、2秒、4秒...
respect_retry_after_header=True
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)
return session
def scrape_with_basic_retry(url):
session = create_session_with_retry()
try:
response = session.get(
url,
timeout=(5, 15), # (接続, 読み取り)
headers={'User-Agent': 'MyBot/1.0'}
)
response.raise_for_status()
return response.text
except requests.exceptions.HTTPError as e:
if e.response.status_code in [404, 403, 401]:
print(f"⚠️ リトライ不要エラー: {e}")
return None
raise
except requests.exceptions.Timeout:
print(f"⏱️ タイムアウト: {url}")
raise
この実装なら、レート制限やサーバーエラーには自動で適切に対応できます。
続きはnoteで
この記事の実装編・詳細解説はnoteで公開しています。
実際のコード例や、実務で遭遇するハマりポイントなど、より踏み込んだ内容を書いています。