HTTPステータスコード「429 Too Many Requests」は、短時間にリクエストを送りすぎたときに返るエラーです。API連携やスクレイピング、バッチ処理の実装で遭遇しやすく、正しいリトライ設計(待機・上限・分散)ができていないと、同じ失敗を繰り返します。この記事では、429が起きる典型パターンと、クライアント側・サーバ側それぞれの実務的な対処を、コード例とあわせて整理します。 結論:429 Too Many Requestsは「あなたのリクエストが多すぎる」とサーバから言われている状態です。サーバ障害ではなく、レート制限による意図的な抑制で、クライアント側の対処で解決できます。直し方の基本は3つ—— もう少し丁寧に説明すると、429 Too Many Requestsは、クライアントが「一定時間内に多すぎるリクエスト」を送ったことを示すHTTPクライアントエラーです。典型的にはレートリミット(Rate limiting)により、サーバが意図的にリクエストを拒否します。 MDNでは、429は「一定時間内に多すぎるリクエストを送った」状況で返り、レスポンスに 「429 Too Many Requests」は、一定時間内に多すぎるリクエストを送信したことを示し、状況によっては「Retry-After」で再試行までの待機時間を示すことがあります。
Retry-After と X-RateLimit-* ヘッダーの読み方429 Too Many Requestsとは?
(1) レスポンスのRetry-Afterヘッダーに従って待つ、
(2) リクエスト頻度・並列数を下げる、
(3) 指数バックオフ+ジッターでリトライする。
Retry-Afterヘッダーが含まれる場合がある、と説明されています。なお429自体は2012年にRFC 6585で追加されたステータスコードです。
同じ「リクエストが通らない」現象でも、429・503・403は意味がまったく違います。最初に切り分けておくと、その後の対処がブレません。 429と503は「いつかは復旧する」前提で待てば良いステータスですが、403は待っても基本的に復旧しません。403が継続するなら、リトライを厚くするより認証情報・UA・IPレピュテーション・規約の確認に時間を割くほうが結果的に近道です。 最も典型的なのは「秒間N回」「分間N回」などの上限を超えるケースです。API提供側の仕様として決まっている場合もあれば、CDN/WAF側(例:Cloudflare等)でIP単位に制限される場合もあります。 平均リクエスト数が少なくても、並列数(同時接続)が多いと429になることがあります。たとえば、キュー未制御で大量の並列フェッチを投げる、スレッド/asyncタスクが増えすぎる、などです。 エラー時に即リトライを繰り返す実装は、サーバ側から見ると「攻撃的な挙動」に近く、さらに厳しい制限や一時ブロックを誘発します。 社内ネットワーク、クラウドのNAT、サーバレスの出口IP共有などで、別アプリのトラフィックと合算され、意図せず上限超過になることがあります。 注意:429は「あなたのプログラムだけ」が原因とは限りません。IP単位・アカウント単位の制限だと、同じ出口を共有する他の処理が原因でも発生します。 429が返ったら、最初にレスポンスヘッダーを確認します。サーバが「いつまで待ってほしいか」「あと何回投げて良いか」をヘッダーで返していることが多く、ここを読まずに固定sleepで対応するのは情報を捨てているようなものです。 Retry-After は、再試行までの待機時間を示すヘッダーです。値の形式は2つあります。 クライアント側は両方の形式を解釈できるようにしておくのが安全です。秒数指定はCDN・APIゲートウェイで多く、時刻指定は計画メンテナンス系で使われがちです。 429 / 503 / 403 の見分け方
ステータス
原因
性質
クライアント側の対処
429 Too Many Requests
あなたのリクエスト頻度が制限を超えた
クライアント単位の抑制
頻度を下げ、リトライ戦略を実装
503 Service Unavailable
サーバ全体の過負荷・メンテナンス
全クライアント共通の障害
少し待ってからリトライ
403 Forbidden
権限不足・アクセス禁止(Bot判定含む)
許可ルールの問題
認証・User-Agent・規約を見直す
よくある原因
レート制限の超過
同時実行が多い
失敗時の無限リトライ
共有IPやNATの影響
まず確認する点
レスポンスヘッダー(Retry-After と X-RateLimit-*)
Retry-After: 120(120秒後にリトライ)Retry-After: Thu, 16 Nov 2026 10:00:00 GMT(指定時刻にリトライ)
もう一つよく出てくるのが X-RateLimit-* 系 です。正式なRFCではなく事実上の慣習ですが、多くのAPIが採用しています。
X-RateLimit-Limit:時間窓内のリクエスト上限X-RateLimit-Remaining:残リクエスト数X-RateLimit-Reset:制限がリセットされる時刻(Unixタイムスタンプもしくは秒数)
これらは「429になる前にペースを落とす」ための予防的な情報源です。Remainingが一桁まで減ってきたら次のリクエストを遅らせる、といった適応的な制御を入れておくと、429そのものを未然に防げます。
制限の単位
制限が「IP単位」なのか「APIキー/ユーザー単位」なのか「エンドポイント単位」なのかで、最適解が変わります。APIドキュメントにクォータや制限の説明がある場合は、仕様に合わせるのが最短です。
ログの粒度
いつ、どのエンドポイントへ、どの頻度で、何並列で叩いたか。ここが追えるだけで原因特定が一気に早まります。
クライアント対策
指数バックオフ
429の代表的な対処は、待機時間を段階的に増やす「指数バックオフ(Exponential Backoff)」です。基本形は、失敗するたびに待機を倍増し、一定回数で打ち切ります。複数クライアントが同時にリトライする「thundering herd」を避けるため、待機時間にランダムな揺らぎ(ジッター)を加えるのが定石です。
Retry-After優先
Retry-Afterがある場合は、それに従うのが最も安全です。HTTP仕様(HTTP Semantics)でも、サーバがRetry-Afterで「どれくらい待つべきか」を伝える用途が示されています。
並列数の上限
スレッド数・async同時実行数・キューのコンシューマ数を制限し、最大同時接続をコントロールします。特にスクレイピングでは、1ホストに対する同時接続を小さく保つのが安定します。
キャッシュと差分取得
同じデータを何度も取得しない設計が重要です。ETag/If-None-MatchやLast-Modified/If-Modified-Sinceを使えるなら、トラフィックを大幅に減らせます。
実装例:自前で書く場合
まずは外部ライブラリを使わず、ロジックを直接書くと次のような形になります。Retry-Afterがあれば優先し、無ければ指数バックオフ+ジッターでリトライする、という骨格です。
import random
import time
import requests
MAX_RETRIES = 6
BASE_DELAY = 1.0 # seconds
url = "https://example.com/api"
for attempt in range(MAX_RETRIES):
r = requests.get(url, timeout=15)
if r.status_code != 429:
r.raise_for_status()
print(r.json())
break
# Retry-After があれば優先
retry_after = r.headers.get("Retry-After")
if retry_after is not None:
try:
wait = float(retry_after)
except ValueError:
# 日付形式などはここでは簡略化(実運用ではパース推奨)
wait = BASE_DELAY
else:
# 指数バックオフ + ジッター
wait = BASE_DELAY * (2 ** attempt) + random.uniform(0, 0.25)
time.sleep(wait)
else:
raise RuntimeError("429が継続したため中断しました")実装例:tenacity を使う場合
本格的なプロジェクトでは、リトライ周りはライブラリに任せたほうが見通しが良くなります。tenacityは、デコレータ1行で指数バックオフ+ジッター・リトライ回数制限を宣言的に書けるライブラリです。
import requests
from tenacity import retry, wait_random_exponential, stop_after_attempt, retry_if_exception_type
class RateLimited(Exception):
pass
@retry(
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(5),
retry=retry_if_exception_type(RateLimited),
reraise=True,
)
def fetch(url):
r = requests.get(url, timeout=15)
if r.status_code == 429:
raise RateLimited(f"429 for {url}")
r.raise_for_status()
return rwait_random_exponential は「2のべき乗にランダムなジッターを掛けた待機時間」を返し、最大60秒で頭打ちにします。RateLimited を独自例外として切り出している理由は、リトライ対象を429に限定し、他のエラーは即座に上位へ伝搬させたいからです。
実装例:backoff を使う場合
もう一つの選択肢が backoff です。requestsの例外をそのまま受け取れ、giveup 条件で「特定ステータスはリトライしない」を表現できます。
import backoff
import requests
def is_fatal(e):
# 4xxのうち429のみリトライ。それ以外の4xxは即座に諦める
if e.response is None:
return False
status = e.response.status_code
return 400 <= status < 500 and status != 429
@backoff.on_exception(
backoff.expo,
requests.exceptions.RequestException,
max_tries=8,
max_time=300,
giveup=is_fatal,
)
def fetch(url):
r = requests.get(url, timeout=15)
r.raise_for_status()
return rtenacityとbackoffは、どちらでも基本は実現できます。細かい制御(戦略の差し替え、リトライ前後のフック)が必要ならtenacity、シンプルな宣言で済むケースならbackoff、くらいの使い分けで問題ありません。
実務の目安:リトライは「回数上限」「最大待機時間」「全体タイムアウト」をセットで設計します。無限リトライは避けてください。
サーバ側の対応
レート制限の設計
提供側で429を返す場合は、利用者が対処しやすいように、どの制限に引っかかったかをログ・ドキュメント・レスポンスのいずれかで示すと運用が安定します。
Retry-AfterとX-RateLimit-*を返す
可能ならRetry-AfterとX-RateLimit-*を返し、再試行タイミングと残量を明示します。クライアントがリトライを制御しやすくなり、結果的にサーバ負荷も下がります。
429と503の使い分け
「明確にレート制限で落としている」なら429が適切です。一方で、過負荷や一時障害で処理できないなら503の方が意味が伝わりやすい場合があります。
WAF / CDN の設定も確認
アプリケーション側で意図的に429を返していないのに429が出ている場合、前段のWAFやCDN(Cloudflare等)のRate Limiting設定が自動的に返している可能性があります。誤って厳しい閾値が入っていないか、Bot対策が過剰反応していないか、ダッシュボードのログから確認するのが近道です。
比較で整理
現場では「何を直すべきか」を素早く切り分けたいはずです。よくある対応を、目的別に整理します。
| 状況 | 優先対応 | 効果 |
|---|---|---|
| 429 + Retry-Afterあり | Retry-Afterに従い待機 | 最短で安定化 |
| X-RateLimit-Remainingが少ない | 事前にペースを落とす | 429を未然に防ぐ |
| 並列が多い | 同時実行数を制限 | 瞬間負荷を抑制 |
| リトライが早すぎる | 指数バックオフ+ジッター | 再発防止に強い |
| 共有IPの疑い | 出口IPの分離/経路見直し | 原因切り分けが可能 |
| 仕様不明の制限 | 提供元のクォータ確認 | 無駄な試行を削減 |
スクレイピングのコツ
429はスクレイピングで頻出です。サイト側の防御は「連続アクセス」「同一IPの集中」「不自然なヘッダー」などを総合して判定することが多く、単に待てば解決するとは限りません。
取得間隔を分散
固定間隔よりも、ジッター(小さなランダム)を混ぜた方が、突発的な集中を避けやすくなります。
必要最小限に絞る
検索結果ページを何度も取りにいく、ページネーションを過剰に深掘りする、といった設計は429を誘発します。差分取得や対象URLの事前フィルタが有効です。
並列度は実測ベースで決める
「何並列で回せばよいか」に正解はありません。1並列・1秒間隔から始めて、429やレスポンス遅延が出ない範囲で徐々に上げ、出始めた手前を運用上限にする、という実測ベースの段階的アプローチが安全です。深夜帯に通った並列度が日中も通るとは限らないので、繁忙時間帯に通る値を採用しましょう。
規約とrobots.txt
技術的に可能でも、サイト規約やrobots.txtに反する取得はトラブルの元です。業務用途では、API提供の有無や許諾の検討も含めて設計します。
解消しないときの判断軸
ここまでの対処を講じても429(あるいは403)が継続する場合、それは「もっと頑張ってリトライする」フェーズではなく、設計や前提を見直すフェーズに入ったサインです。
規約違反でブロックされている可能性
レート制限の閾値を十分に下げても429や403が継続するなら、サイト側がそのアクセス元を意図的に遮断している可能性があります。見極めのポイントは次の通りです。
- 時間を置いても解除されない:通常のレート制限は数分〜数時間で解除されますが、ブロックの場合は数日〜永続のことも
- 低レートでも継続する:1分に1リクエスト程度まで落としても継続するなら、レート制限以外の要因の可能性が高い
- 別IP・別環境からは正常にアクセスできる:自分のIPだけ弾かれているなら、IP単位のブロックリストに入っている
このフェーズに入ったら、技術的な回避策に時間を使うより、利用規約を再確認し、必要に応じて運営側へ正規ルートで問い合わせるほうが結果的に近道です。
商用APIへの切り替えを検討する
取得対象のサイトが商用APIを提供しているなら、APIへ切り替えるかどうかの判断軸は次の通りです。
- 取得頻度・データ量がAPIの料金プランに収まるなら、迷わずAPIへ。安定性・法務リスク・運用工数のいずれの観点でも有利
- APIではカバーされない項目(一部の検索条件・古いデータ等)が要件にあるなら、API+スクレイピングのハイブリッドで設計
- 料金が業務上見合わない場合は、取得頻度を落とす・対象を絞るといった要件側の調整を先に検討する
「APIがあるのにスクレイピングを続ける」のは、技術選択というより業務設計の問題です。短期的にはコストが安く見えても、規約違反リスク・アンチボット対策のいたちごっこ・保守工数を含めた中長期コストではAPIが優位なケースが大半です。
スクレイピングの設計・運用でお困りではありませんか?
Wilicoでは、レート制限を踏まえた負荷設計・リトライ戦略の実装・アンチボット対策・運用監視・法務確認まで、スクレイピング案件を一貫してご支援しています。自社内製では手が回らない箇所だけ切り出して任せたい、業務として安定運用に乗せたい、といったお悩みがあればお気軽にご相談ください。
まとめ
- 429 Too Many Requestsは「短時間にリクエストを送りすぎた」ことを示す
- 503(サーバ過負荷)・403(権限不足)とは別物。まず切り分ける
- レスポンスヘッダー(
Retry-AfterとX-RateLimit-*)を必ず読む - 指数バックオフ+ジッター、並列制御、キャッシュで再発を防ぐ
- Pythonなら
tenacityまたはbackoffでリトライを宣言的に書ける - 共有IPやWAF/CDNの制限など、実装以外の要因も疑う
- 低レートでも解消しないなら、規約違反ブロックや商用API切り替えの判断軸へ
仕様の意味やヘッダーの役割は、公式ドキュメントを確認しておくと判断が早くなります。
本記事の内容は執筆時点(2026年5月)の情報に基づいています。HTTP仕様・各サービスのレート制限値・関連ライブラリは随時更新されるため、業務利用の際は最新の公式ドキュメントを必ずご確認ください。