На днях по работе потребовалось сделать утилиту, которая прямо вот из консоли ходит в апи нашего клауд сервиса и берет оттуда кое-какую информацию. Подробности что и зачем - вне этого рассказа. Принципиальный вопрос здесь другой - скорость. Скорость реально важна (порядок количества запросов - десятки и сотни). Потому что ждать - не кайф.Здесь я хочу поделиться своим ресёрчем на тему запросов, как делать круто, а как нет. С примерами кода конечно. А так же рассказать, как я тупил.  Последовательные синхронные запросы. Будем использовать всем известную либу requests и tqdm для красивого вывода в консоль. В качестве игрушечного примера выбрал первую-попавшуюся публичную апишку: https://catfact.ninja/. Метрикой качества будет RPS (Request per second). Чем выше - тем соотвественно лучше. Получаем следующий вывод в терминале: RPS - 1.5. Очень грустно. У меня еще и интернет не самый быстрый сейчас дома. Ну тут добавить нечего.Что можно оптимизировать уже сейчас? Ответ: использовать requests.Session Eсли делать несколько запросов к одному и тому же хосту, базовое TCP-соединение будет использоваться повторно, что приводит к значительному увеличению производительности. (цитата из документации requests) Немного изменив метод, и вызвав его, видим следующее: Почти 4 RPS, в сравнении с 1.5 уже прорыв.Но не секрет, что для сокращения i/o time есть практика использования асихнронных/многопоточных программ. Это как раз такой случай, потому что во время ожидания ответа от сервера наша программа ничего не делает, хотя могла бы отправлять уже другой запрос, а потом другой и т.д. Попробуем реализовать асинхронный подход к решению кейса. Для удобства вызовов сделаем функцию-оболочку: И собственно сама реализация метода (не забудьте поставить aiohttp, обычные реквесты не работают в асинхронной парадигме): Видим: Удивительно, но разницы с предыдущим случаем особо нет. Я думаю выигрыш в i/o тайме компенсируется издержками на передачу управления потоком функциями друг другу (слышал что очень ругают эти моменты в python). Как на самом деле не знаю.На словах такой подход можно объяснить так. - В цикле создается корутина, которая отправляет запрос.- Не дожидаясь ответа, управление потоком отдается снова event loop'у, который создает следующую по "for циклу" корутину, которая тоже отправляет запрос.- Но теперь прежде чем отдать управление эвент лупу проверяется статус ответа первой корутины. Если она может быть продолжена (получила ответ на запрос), то управление потоком возвращается ей, если нет, то см. пункт 2.- И так далееВ итоге код-то впринципе асихнронный, но запросы не отправляются "разом". Принципиально иначе подойти к этой ситуации поможет asyncio.gather. Gather - как ни банально с английского собирать. Метод gather собирает коллекцию корутин и запускает их разом (тоже условно конечно). То есть, в отличии от предыдущего случая, мы в цикле создаем корутины, а потом их запускаем.Было:[cоздали корутину] -> [запустили корутину] -> [cоздали корутину] -> [запустили корутину] -> А стало:[cоздали корутину] -> [создали корутину] ->[cоздали корутину] -> [создали корутину] -> И получаем... получаем... ничего не получаем. Курсор продолжает многозначительно мигать в окне терминала. Не работает - подумал Штирлиц.Путем мучительного дебага и попыток понять, почему мой код не работает, я понял - причина в моем VPN. Его узлы находятся где-то в юрисдикции Cloudflare. А они такое поведение не поощряют, считая, что я бот. Нормальный человек столько запросов в секунду делать не будет, поэтому мои запросы...теряются где-то в пучинах интернета. Ответа на них не будет. Никогда. Корутины просто не заканчиваются.Окей, поняв откуда ноги растут, запускаем код: Цифры выросли, круто, 21 не 1.5 - это уж точно.Однако же есть какой-то предел, увеличим N (число запросов) до 200. Это что же получается, можно и так? На самом деле нет. Если внимательно рассмотреть, что же все-таки нам отвечает сервер, то заметим, что большая часть ответов это {'message': 'Rate Limit Exceeded', 'code': 429}Конкретный лимит запросв, который я установил в ходе эксперимента с этим сервисом - это около 60 ответов за раз, остальное он не переваривает. Так что если будете так ходить в сервисы, которые не хотите перегрузить или вообще положить, то подходите к этом вопросу обдуманно, не превышайте определенных рамок. Тут особо смысла нет - практический эксперимент показал, что threading показывает такие же результаты (плюс - минус), как асихнронный код из пункта 3.  Не буду врать, просто посмотрел как нечто похожее делал какой-то индус с Ютуба. Результаты сильно хуже, чем у предыдущих способов. Да и писать такой код - это насилие над своей психикой. А я свою психику берегу. На днях упал Cloudlfare - извините, это из-за меня, больше не буду. Хотите быть чемпионом по запросам - используйте asyncio.gather, но с тщательно подобранными лимитами. Если вы ходите не на один хост, а в разные источники, то вообще не стестяйтесь.