Более быстрый способ делать асинхронные запросы
Я пытаюсь использовать requests-futures
библиотека для отправки пакета асинхронных HTTP-запросов и определения наличия или отсутствия определенной строки в содержимом каждой страницы.
Вот синхронная версия. Обратите внимание, что реальный сайт, который я добавляю, не является переполнением стека, а длина URL-адресов в действительности составляет около 20000. В приведенном ниже примере, я в среднем примерно 1 секунду времени стены за цикл, что означает, что вся партия будет занимать полдня с такой скоростью.
import timeit
import requests
KEY = b'<meta name="referrer"'
def filter_url(url):
"""Presence or absence of `KEY` in page's content."""
resp = requests.get(url, stream=True)
return resp.content.find(KEY) > -1
urls = [
'https://stackru.com/q/952914/7954504',
'https://stackru.com/q/48512098/7954504',
'https://stackru.com/q/48511048/7954504',
'https://stackru.com/q/48509674/7954504',
'https://stackru.com/q/15666943/7954504',
'https://stackru.com/q/48501822/7954504',
'https://stackru.com/q/48452449/7954504',
'https://stackru.com/q/48452267/7954504',
'https://stackru.com/q/48405592/7954504',
'https://stackru.com/q/48393431/7954504'
]
start = timeit.default_timer()
res = [filter_url(url) for url in urls]
print(timeit.default_timer() - start)
# 11.748123944002145
Теперь, когда я иду делать это асинхронно:
from requests_futures.sessions import FuturesSession
session = FuturesSession()
def find_multi_reviews(urls):
resp = [session.get(url).result() for url in urls]
print(resp)
return [i.content.find(KEY) > -1 for i in resp]
start = timeit.default_timer()
res2 = find_multi_reviews(urls)
print(timeit.default_timer() - start)
# 1.1806047540012514
Я могу получить ускорение в 10 раз. Это нормально, но я могу сделать лучше? На данный момент, я все еще смотрю чуть менее 2 часов времени выполнения. Есть ли уловки, такие как увеличение числа рабочих или выполнение в отдельных процессах, которые приведут к улучшению скорости здесь?
2 ответа
При дальнейшем исследовании это выглядит так, будто в этом случае я привязан к процессору, а не к сети.
Это заставило меня поверить ProcessPoolExecutor
обеспечит улучшение здесь. Тем не менее, то, что я закончил, это просто создание урезанной версии напрямую с concurrent.futures
, Это снова сократило время пополам:
def filter_url(url):
"""Presence or absence of `KEY` in page's content."""
resp = requests.get(url, stream=True)
return resp.content.find(KEY) > -1
def main():
res = []
with ProcessPoolExecutor() as executor:
for url, b in zip(urls, executor.map(filter_url, urls)):
res.append((url, b))
return res
start = timeit.default_timer()
res = main()
print(timeit.default_timer() - start)
# 0.5077149430002464
Если вы привязаны к IO (сети), а не к процессору, вы можете легко увеличить количество используемых потоков:
session = FuturesSession(max_workers=30)
# you can experiment with the optimal number in your system/network
Надеюсь, это поможет!