プールの map はワーカーに一度に 1 つの項目を渡します。その項目を、URL と選択済みプロキシを含む完全なジョブにしてください。ワーカーが変化するグローバルリストから経路を推測しないようにするためです。

有限個の urls には、検証済みプロキシ URL の空でないリストを指定します。公開 HTTP 候補はHTTP プロキシリストで入手できます:

from concurrent.futures import ThreadPoolExecutor
from itertools import cycle
import requests

def fetch(job):
    url, proxy_url = job
    proxies = {"http": proxy_url, "https": proxy_url}
    with requests.get(url, proxies=proxies, timeout=(5, 15)) as response:
        response.raise_for_status()
        return url, response.text

if not proxy_urls:
    raise ValueError("No proxy endpoints were supplied")

jobs = zip(urls, cycle(proxy_urls))
with ThreadPoolExecutor(max_workers=5) as executor:
    for url, body in executor.map(fetch, jobs):
        handle_result(url, body)

urls、proxy_urls、handle_result はアプリケーションが提供します。コーディネーターはエンドポイントを繰り返しの順序で割り当て、各ワーカーは 1 つの固定ペアを受け取ります。結果を反復すると、ワーカーの例外も表面化します。

5 つのワーカーが制限するのはアクティブなジョブであり、プロキシごとのアクティブなジョブではありません。リストに 2 つのエンドポイントがあれば、複数のジョブが同じエンドポイントを同時に使えます。必要な場合はエンドポイントごとの容量ルールを別途追加してください。エンドポイント URL ごとに 1 つのアクティブなジョブにするには、同期キューのリースが各エントリを予約して返せます。

再試行は同じ URL に対して新しい上限付きの試行を作るべきです。共有リストからエントリを削除する再帰的な再試行呼び出しは避けてください。別のワーカーがそこから選択している可能性があり、使い果たしたリストにも明示的な結果が必要です。

関連記事