为每个 Python ThreadPoolExecutor 任务分配 URL 和代理
在提交 Python 请求任务之前分配代理,而不是让工作线程去争抢共享的可变选择状态。
线程池的 map 每次只把一个条目交给工作线程。请把该条目做成完整任务,包含 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 由应用程序提供。协调器按重复顺序分配端点;每个工作线程拿到一个固定的配对。遍历结果时也会暴露工作线程的异常。
五个工作线程限制的是活跃任务数,而不是每个代理的活跃任务数。如果列表里有两个端点,多个任务可能同时使用同一个端点。需要时请另外添加按端点计算的容量规则。若要让每个端点 URL 只有一个活跃任务,同步队列租约可以预订并归还每个条目。
重试应为同一个 URL 创建新的、有界的一次尝试。避免使用会从共享列表中删除条目的递归重试调用:另一个工作线程可能正在从中选择,而耗尽的列表仍然需要明确的结果。