Сбор каждого результата из параллельных прокси-запросов urllib3
Совместно используйте настроенный ProxyManager между рабочими потоками, потребляйте итератор результатов исполнителя и отличайте удержание пула от лимита соединений.
Создание пула рабочих не передаёт вызывающему загруженные страницы. Сохраняйте и потребляйте значения, которые возвращает отправленная работа:
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; настройку одного пула не следует описывать как единую глобальную квоту сокетов для каждого хоста.