Более быстрый способ делать асинхронные запросы

Я пытаюсь использовать 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

Надеюсь, это поможет!

Другие вопросы по тегам