Используйте синхронный, многопоточный, очередной и асинхронный цикл обработки событий Python, чтобы сделать 100 HTTP-запросов и посмотреть, какое решение работает лучше всего.
Легко отправить один HTTP-запрос с помощью requests
package. Что, если я хочу отправлять сотни или даже миллионы HTTP-запросов асинхронно? Эта статья представляет собой обзорную заметку, чтобы найти мой самый быстрый способ отправки HTTP-запросов.
Код работает на хосте виртуальной машины Linux (Ubuntu) в облаке с Python 3.7. Весь код в сути готов к копированию и запуску.
Решение # 1: синхронный путь
Самый простой и понятный способ, но и самый медленный. Я подделал 100 ссылок для теста этим волшебным оператором списка Python:
url_list = ["https://www.google.com/","https://www.bing.com"]*50
Код:
Загрузка 100 ссылок занимает около 10 секунд.
... download 100 links in 9.438416004180908 seconds
В качестве синхронного решения еще есть над чем работать. Мы можем использовать объект Session
для дальнейшего увеличения скорости. Объект Session будет использовать пул соединений urllib3, что означает, что для повторения запросов к одному и тому же хосту будет повторно использоваться базовое TCP-соединение Session
object, что приведет к увеличению производительности.
Поэтому, если вы делаете несколько запросов к одному и тому же хосту, базовое TCP-соединение будет повторно использовано, что может привести к значительному увеличению производительности - Объекты сеанса
Чтобы гарантировать выход объекта запроса независимо от успеха или нет, я собираюсь использовать with
statement в качестве оболочки. with
keyword в Python - это просто чистое решение для замены try… finally…
.
Давайте посмотрим, сколько секунд можно сэкономить, изменив на это:
Похоже, производительность действительно увеличена до 5.x секунд.
... download 100 links in 5.367443561553955 seconds
Но это все равно медленно, давайте попробуем многопоточное решение.
Решение № 2: Многопоточность
Многопоточность Python - опасная тема для обсуждения, иногда многопоточность может быть даже медленнее! Дэвид Бизли представил замечательную презентацию, посвященную этой опасной теме. вот ссылка на Youtube.
В любом случае, я все еще собираюсь использовать Python Thread для выполнения задания HTTP-запроса. Я буду использовать очередь для хранения 100 ссылок и создать 10 рабочих потоков загрузки HTTP для асинхронного использования 100 ссылок.
Чтобы использовать объект Session, создавать 10 объектов Session для 10 потоков - пустая трата времени, мне нужен один объект Session и повторно использовать его для всех операций по загрузке. Чтобы это произошло, код будет использовать local
object из threading
package, так что 10 потоковых работников будут совместно использовать один объект Session.
from threading import Thread,local ... thread_local = local() ...
Код:
Результат:
... download 100 links in 1.1333789825439453 seconds
Это быстро! намного быстрее, чем синхронное решение.
Решение # 3: многопоточность с помощью ThreadPoolExecutor
Python также предоставляет ThreadPoolExecutor
для выполнения многопоточной работы, мне очень нравится ThreadPoolExecutor.
В версии Thread и Queue есть цикл while True
в работнике HTTP-запроса, это делает рабочую функцию запутанной с Queue и требует дополнительного изменения кода с синхронной версии на асинхронную версию.
Используя ThreadPoolExecutor и его функцию карты, мы можем создать многопоточную версию с очень кратким кодом, требующим минимального изменения кода по сравнению с синхронной версией.
Код:
И вывод такой же быстрый, как и версия Thread-Queue:
... download 100 links in 1.0798051357269287 seconds
Решение no 4: asyncio с aiohttp
Все говорят, что за asyncio
будущее, и оно скоро. Некоторые используют его, делая 1 миллион HTTP-запросов с Python asyncio
и aiohttp
. Хотя asyncio
очень быстро, он использует нулевую многопоточность Python.
Вы не поверите, но asyncio работает в одном потоке, в одном ядре процессора.
Цикл событий, реализованный в asyncio
, почти то же самое, что любимый в Javascript.
Asyncio настолько быстр, что может отправлять практически любое количество запросов на сервер, единственное ограничение - это ваш компьютер и пропускная способность интернета.
Слишком много отправленных HTTP-запросов будут вести себя как «атакующие». Некоторые веб-сайты могут заблокировать ваш IP-адрес, если будет обнаружено слишком много запросов, даже Google заблокирует вас. Чтобы меня не забанили, я использую настраиваемый объект TCP-коннектора, в котором максимальное TCP-соединение задано только равным 10. (можно безопасно изменить его на 20)
my_conn = aiohttp.TCPConnector(limit=10)
Код довольно короткий и лаконичный:
И приведенный выше код завершил загрузку 100 ссылок за 0,74 секунды!
... download 100 links in 0.7412574291229248 seconds
Обратите внимание: если вы запускаете код в JupyterNotebook или IPython. пожалуйста, также установите пакет thenest-asyncio
. (Благодаря этой ссылке на StackOverflow. Благодарим Диафа Бадреддина.)
pip install nest-asyncio
и добавьте следующие две строки кода в начало кода.
import nest_asyncio nest_asyncio.apply()
Решение №5: А как насчет NodeJS?
Мне интересно, а что, если я проделаю ту же работу в Nodejs, у которого есть встроенный цикл обработки событий?
Вот полный код.
Это занимает от 1,1 до 1,5 секунд, вы можете запустить его, чтобы увидеть результат на своей машине.
... test: 1195.290ms
Python, выиграй игру на скорость!
(Похоже, пакет Node запроса устарел, но этот образец предназначен только для тестирования того, как цикл событий NodeJs работает по сравнению с циклом событий Python.)
Дайте мне знать, если у вас есть лучшее / более быстрое решение. Если у вас есть вопросы, оставьте комментарий, и я постараюсь ответить. Если вы заметили ошибку или ошибку, не стесняйтесь отмечать их. Спасибо за прочтение.