Pythonの「Max retries exceeded」ConnectionErrorを解決する方法

intermediate🐍 Python2026-04-08| Python 3.x, requestsライブラリ, Windows/macOS/Linux

Error Message

requests.exceptions.ConnectionError: HTTPSConnectionPool(host='...', port=443): Max retries exceeded with url
#python#requests#api開発#ネットワーク

エラーが発生する理由

PythonでウェブデータのスクレイピングやAPIの呼び出しを行ったことがあるなら、おそらくこの壁に突き当たったことがあるでしょう。このエラーは単一の問題ではなく、数回の試行後もサーバーとのハンドシェイクを確立できなかったことを requests の呼び出しが示しているシグナルです。

デフォルトでは、ライブラリは即座に接続を試みます。ネットワークのジッター(揺らぎ)やサーバーの混雑がわずか100ミリ秒でも発生すると、接続は失敗します。これらの失敗があらかじめ定義された制限(「最大リトライ回数」)に達すると、Pythonはスクリプトが永久にループするのを防ぐために、あきらめてこの例外をスローします。

一般的な根本原因

  • 過度なレート制限: GitHubやTwitterのようなAPIは、多くの場合、ユーザーを1時間あたり5,000リクエストに制限しています。これを超えると、サーバーは接続を完全に切断することがあります。
  • DNS解決の失敗: ローカルマシンがドメイン名をIPアドレスに変換できない場合です。多くの場合、 /etc/hosts ファイルの設定ミスや、8.8.8.8のような低速なDNSプロバイダーが原因です。
  • ネットワークの不安定さ: 公共のWi-Fiにおける高いパケットロスや、不安定なVPN接続により、TCPハンドシェイクが途中で終了してしまうことがあります。
  • WAFによるブロック: Cloudflareのようなセキュリティレイヤーが、デフォルトのPythonヘッダーを検知し、トラフィックをボットとしてフラグを立てる場合があります。
  • ソケットの枯渇: for ループ内で1,000個の個別の接続を閉じずに開くと、最終的にOSの利用可能なポートが不足します。

解決策1: 指数バックオフを使用する

最も賢明な解決策は、Pythonに「待つ」ように指示することです。即座に失敗させるのではなく、 HTTPAdapter を使用して、待ち時間を増やしながらリクエストを再試行します。このアプローチにより、502や504エラーを適切に処理できます。

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def fetch_data(url):
    session = requests.Session()
    
    # total=5、backoff_factor=1に設定すると、Pythonは試行間に
    # 1秒、2秒、4秒、8秒、そして16秒待ちます。
    retry_strategy = Retry(
        total=5,
        backoff_factor=1,
        status_forcelist=[429, 500, 502, 503, 504],
        allowed_methods=["HEAD", "GET", "OPTIONS"]
    )
    
    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount("https://", adapter)
    session.mount("http://", adapter)

    try:
        response = session.get(url, timeout=10)
        response.raise_for_status()
        return response.json()
    except requests.exceptions.ConnectionError as ce:
        print(f"Connection failed: {ce}")
    except requests.exceptions.Timeout:
        print("The server took too long to respond.")
    except requests.exceptions.RequestException as e:
        print(f"A general error occurred: {e}")

# 使用例
data = fetch_data("https://api.example.com/v1/resource")

解決策2: 実際のブラウザを模倣する

多くのサーバーは、自身を python-requests/2.28.1 と識別するリクエストを自動的に拒否します。標準的なChromeやFirefoxブラウザのように見える User-Agent ヘッダーを追加することで、これらの基本的なフィルターをバイパスできます。

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36'
}

# これにより、リクエストがWindows 10のデスクトップから送信されているように見えます
response = requests.get("https://api.example.com/data", headers=headers)

解決策3: セッションで接続を再利用する

リクエストごとに新しい接続を作成するのはコストがかかり、失敗の原因になりやすいです。 requests.Session() オブジェクトを使用しましょう。これにより、同じホストへの複数のリクエストに対して基盤となるTCP接続が維持され、ハンドシェイクエラーの可能性が大幅に減少します。

# コンテキストマネージャーを使用すると、セッションの終了が自動的に処理されます
with requests.Session() as session:
    for item_id in range(1, 101):
        try:
            # 100回すべてのリクエストで同じ接続プールを再利用します
            resp = session.get(f"https://api.example.com/items/{item_id}", timeout=5)
            process_data(resp.json())
        except requests.exceptions.ConnectionError:
            print(f"Skipping item {item_id} due to connection failure.")
            continue

解決策4: プロキシ設定を構成する

企業環境では、特定のプロキシを経由しない限り、トラフィックがファイアウォールによってブロックされることがあります。接続の中断を避けるために、スクリプト内でこれらの設定をグローバルに定義できます。

proxies = {
  'http': 'http://proxy.company.com:8080',
  'https': 'http://proxy.company.com:1080',
}

requests.get("https://api.example.com", proxies=proxies)

プロのヒント: サブネットを確認する

問題がコードではなく、ルーティングの競合である場合があります。ローカルの開発環境で頻繁に発生する接続断をトラブルシューティングする際、私は サブネット計算機 を使用します。これにより、APIエンドポイントが、到達するために特定のゲートウェイやVPNルートを必要とする制限されたサブネット内に存在するかどうかを確認できます。

修正を確認する方法

単にうまくいくことを願うのではなく、ログを確認しましょう。詳細なデバッグを有効にして、すべての接続試行とリトライをリアルタイムで確認できます。

import logging
import http.client as http_client

# 基盤となるurllib3ライブラリの詳細なデバッグを有効にします
http_client.HTTPConnection.debuglevel = 1
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True

出力を観察してください。「Retrying (Retry(total=4...))」と表示されれば、バックオフ戦略が機能しています。3回目の試行でスクリプトが成功すれば、一時的なネットワーク問題を正常に軽減できたことになります。

まとめのチェックリスト

  • タイムアウトを設定する: スクリプトが数分間ハングするのを防ぐために、常に timeout=10 (または同様の設定)を使用してください。
  • セッションを閉じる: システムリソースを解放するために、コンテキストマネージャー( with )を使用してください。
  • サーバーを尊重する: 429エラーが発生した場合は、 backoff_factor を2または3に増やして、サーバーに余裕を持たせてください。

Related Error Notes