install_opener()を2回呼んでも、2つ目のopenerが1つ目に追加されるわけではありません。モジュールのデフォルトopenerを置き換えます。あるオブジェクトがプロキシを、別のオブジェクトがウェブサイトのログインを知っているなら、必要なハンドラーを含む単一のopenerを構築してください。

import os
from urllib.request import (ProxyHandler, HTTPBasicAuthHandler,
                            HTTPPasswordMgrWithDefaultRealm, build_opener)

passwords = HTTPPasswordMgrWithDefaultRealm()
passwords.add_password(None, 'https://example.com/private/',
                       os.environ['SITE_USER'], os.environ['SITE_PASSWORD'])
opener = build_opener(
    ProxyHandler({'https': 'http://203.0.113.10:8080'}),
    HTTPBasicAuthHandler(passwords),
)
with opener.open('https://example.com/private/report', timeout=10) as response:
    body = response.read()

プロキシと宛先はあくまで例です。公開HTTPプロキシ経由で到達できる公開サービスについては、HTTPプロキシ一覧がルーティングハンドラー用の候補エンドポイントを提供します。このopenerはHTTPSリクエストをHTTPプロキシ経由でルーティングし、宛先のBasicチャレンジを処理します。プロキシ認証は設定しません。

プロキシにもログインが必要な場合は、対応しているプロキシ認証の方式を別途選んでください。CONNECT時の407は、宛先が独自のチャレンジを発行できるより前に発生します。パスワードマネージャーにウェブサイトの資格情報を追加しても、その前段のやり取りは満たせません。

opener.open()を直接呼ぶと設定が操作とともに保たれます。アプリケーションがすべての便利な呼び出しでこのopenerを共有することを意図的に望まない限り、モジュール全体のデフォルトを変更する必要はありません。

関連記事