ワーカープールを作成しても、呼び出し元にダウンロード済みのページが渡されるわけではありません。送信した処理が返す値を保持して消費します:

from concurrent.futures import ThreadPoolExecutor
import urllib3

manager = urllib3.ProxyManager(
    'http://proxy.example:8080',
    maxsize=4,
    block=True,
)

def fetch(url):
    response = manager.request(
        'GET', url, timeout=urllib3.Timeout(connect=5, read=15),
    )
    return url, response.status, response.data

try:
    with ThreadPoolExecutor(max_workers=4) as workers:
        results = list(workers.map(fetch, urls))
finally:
    manager.clear()

この断片は、限られた URL リストと通常のレスポンスサイズを前提としています。プロキシ認証なしで許可された公開 URL チェックには、HTTP プロキシリストの候補がマネージャのサンプル HTTP エンドポイントを置き換えられます。各結果には URL が含まれるため、呼び出し元はステータスと本文を正しいリクエストに関連付けられます。map は入力を順に結果を返します。ワーカー内の例外は、その結果を取り出すときに送出されます。

設定済みの ProxyManager がリクエストの接続プールを所有します。ここで使う既定のプリロード応答モードでは、urllib3 が本文を読み取り、接続をプールに戻します。ストリーミング版では明示的な消費と接続の解放が必要です。

maxsize は通常、保持される再利用可能な接続を制御するもので、開かれるすべてのソケットに対する厳格な上限ではありません。block=True は該当するプールの動作を変え、呼び出し元はそのプールの上限を超えて追加で開くのではなく、空き接続を待ちます。

この例ではワーカー数が同時呼び出しを制限します。宛先が多い場合、プールの構成と HTTPS トンネルに使う経路も重要です。1 つのプールの設定を、すべてのホストに対する単一のグローバルなソケット割当てとして説明すべきではありません。

関連記事