#python-3.x #optimization #python-requests
#python-3.x #оптимизация #python-запросы
Вопрос:
Я выполняю задание для отправки запросов на 1000 конкретных веб-сайтов (некоторые из которых, похоже, больше не существуют) в Python (3) с помощью метода HEAD и сообщаю статистику о заголовках их ответов. Скрипт должен завершиться через пять минут. Очевидно, что запросы могут занимать меньше времени, уменьшая время ожидания, но чем больше вы уменьшаете время ожидания, тем больше возникает ошибок времени ожидания, и их перехват кажется очень дорогостоящим. Например, когда время ожидания составляло 0,3 секунды, было 700 хороших запросов и 300 ошибок тайм-аута, а общее время, затраченное на обнаружение ошибок тайм-аута, само по себе превышало пять минут. Уменьшение времени ожидания действительно сокращает время для обнаружения каждой ошибки тайм-аута, поскольку requests приходится ждать тайм-аута, прежде чем выдавать ошибку, но количество тайм-аутов также увеличивается. Мне удалось получить общее время, затраченное на обнаружение ошибок тайм-аута, меньше пяти минут при тайм-ауте = 0,05 и тайм-ауте = 0,03, но общее время, включая время, затраченное на запросы, все еще превышало пять минут. тайм-аут = 0,02 привел к тому, что были доступны только 20 сайтов с общим временем обработки ошибок 5: 17, а тайм-аут = 0,01 привел к тому, что сайты не были доступны. Человек, который дал задание, настаивает на том, что это возможно, поэтому я, должно быть, делаю что-то не так. Я попытался использовать запросы.Объект сеанса, но это не привело к какому-либо заметному ускорению. Что еще я могу сделать, чтобы ускорить процесс?
Комментарии:
1. Разрешено ли / предполагается ли использовать потоковую обработку? Простой
concurrent.futures.ThreadPoolExecutor
, вероятно, можно было бы использовать для распараллеливания ваших запросов, чтобы вы не зависали в ожидании на самом медленном сервере; егоmap
метод очень упростил бы это.
Ответ №1:
Реальный ответ — использовать асинхронные HTTP-запросы. Но чтобы этически ответить на этот вопрос, я должен настаивать на низком пределе для домена для одновременных запросов, иначе вы можете перегрузить серверы (и попасть в черный список).
Ниже приведен (непроверенный) пример реализации с использованием aiohttp
, который поддерживает настраиваемое количество максимального параллелизма, а также максимальный параллелизм на домен.
import aiohttp
import asyncio
from collections import Counter
NUM_PARALLEL = 64
MAX_PARALLEL_PER_DOMAIN = 4
TIMEOUT = aiohttp.ClientTimeout(total=60)
async def fetch_url(url, session):
try:
async with session.get(url) as response:
# Whatever you want.
return {
"url": url,
"status": response.status,
"content-type": response.headers["content-type"]
}
except aiohttp.ServerTimeoutError:
return {"url": url, "status": "timeout"}
except Exception as e:
return {"url": url, "status": "uncaught_exception", "exception": e}
domain_num_inflight = Counter()
domain_semaphore = {}
async def worker(urls, results):
async with aiohttp.ClientSession(timeout=TIMEOUT) as session:
while urls:
url = urls.pop()
domain = urlparse(url).netloc
if domain_num_inflight[domain] == 0:
domain_semaphore[domain] = asyncio.Semaphore(MAX_PARALLEL_PER_DOMAIN)
domain_num_inflight[domain] = 1
async with domain_semaphore[domain]:
results.append(await fetch_url(url, session))
domain_num_inflight[domain] -= 1
if domain_num_inflight[domain] == 0: # Prevent memory leak.
del domain_semaphore[domain]
del domain_num_inflight[domain]
urls = [...]
worklist = urls[:]
results = []
workers = [worker(worklist, results) for _ in range(NUM_PARALLEL)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(*workers))
print(results)
Комментарии:
1. Спасибо, я изучу эти библиотеки. Если я нажимаю на каждый URL-адрес только один раз (например, назначая каждому рабочему совершенно другой список URL-адресов), нужен ли семафор?
2. @faiuwle Важной частью является то, что вы обращаетесь к каждому серверу только один раз. Например. если ваша программа запрашивает данные с сервера много раз в секунду, вы, скорее всего, попадете в черный список (или, что еще хуже, сообщите об этом правоохранительным органам).
3. Однако это простые домены, и все они разные — можно с уверенностью предположить, что разные домены не находятся на одном сервере, верно?
4. @faiuwle Нет, но это «достаточно хорошо». Если бы вы писали средство проверки состояния промышленного уровня, вы, вероятно, также включили бы отдельный модуль разрешения IP и сделали больше ограничения скорости на основе IP-адреса (блока).
5. Привет, я все еще борюсь с этим. Когда я использую loop.run_until_complete(asyncio.gather(…)), время ожидания немедленно истекает. Я даже пытался использовать wait_for, чтобы установить определенное время ожидания в пять минут, но оно все равно истекает немедленно. Есть ли какая-то длина тайм-аута по умолчанию, которую я должен как-то переопределить?