認証情報が正しくても、@ や # を含むパスワードは手作業で組み立てたプロキシ URL を壊すことがあります。これらの文字には URL 内で意味があるため、クライアントがパスワードの一部をホスト名やフラグメントとして解析する可能性があります。

二つの認証情報フィールドを別々にエンコードする

この例は URL の構造を区切る文字をそのままにし、ユーザー名とパスワードだけをエンコードします。

from urllib.parse import quote

username = "user@team"
password = "p:a/ss#?"
proxy_url = (
    "http://"
    + quote(username, safe="")
    + ":"
    + quote(password, safe="")
    + "@203.0.113.10:8080"
)

実際のエンドポイントと認証情報を使ってください。表示したアドレスは文書用の例です。safe 引数を空にすることが重要です。quote は通常スラッシュをエスケープしませんが、認証情報フィールド内のスラッシュはエンコードする必要があります。

プロキシ URL 全体をエンコードしないでください。スキームの区切りや認証情報とホストの間の @ をエンコードすると、保ちたかった構造が壊れます。すでにエンコード済みのパスワードを二重にエンコードすることも避けてください。

結果を Requests で使う

import requests

response = requests.get(
    "https://example.com/",
    proxies={"http": proxy_url, "https": proxy_url},
    timeout=(5, 15),
)
response.raise_for_status()

まだ失敗する場合は、プロキシがログインを拒否したのか、それ以前の解析や接続でクライアントが失敗したのか確認します。407 の認証要求と無効な URL は異なる障害です。

HTTPX では認証を分けて扱える

HTTPX の Proxy オブジェクトは、エンドポイントと認証情報を別々の引数で受け取れます。

import httpx

proxy = httpx.Proxy(
    "http://203.0.113.10:8080",
    auth=("user@team", "p:a/ss#?"),
)

with httpx.Client(proxy=proxy, trust_env=False, timeout=15) as client:
    response = client.get("https://example.com/")
    response.raise_for_status()

これにより userinfo 部分を自分で組み立てずに済みます。認証情報をプロキシ接続とは別に暗号化するものでも、すべてのサービスが対応する認証方式を選ぶものでもありません。

無料リストが IP とポートしか提供していない場合、この例がデフォルトの認証情報を与えるわけではありません。運営者が実際にアカウントを提供したときだけ認証を使ってください。認証情報を含む URL は共有ログやソースファイルに残さないでください。

関連記事