Подробное сравнение этих двух библиотек Python
Делиться

Если вы когда-либо занимались HTTP-вызовами в Python, скорее всего, вы использовали библиотеку Requests. На протяжении многих лет Requests был фактическим стандартом, известным своей относительной простотой и ставшим краеугольным камнем бесчисленных приложений на Python. От простых скриптов до более сложных веб-сервисов, его синхронная природа отлично подходит для самых разных типов приложений.
Однако экосистема библиотек Python постоянно развивается, особенно с развитием асинхронного программирования с использованием asyncio. Этот сдвиг открыл двери новым библиотекам, предназначенным для использования неблокирующего ввода-вывода для повышения производительности, особенно в приложениях, требовательных к вводу-выводу.
Вот тут-то и пригодится библиотека HTTPX — относительный новичок, позиционирующий себя как «HTTP-клиент следующего поколения для Python», предлагающий как синхронные, так и асинхронные API, а также поддержку современных веб-функций, таких как HTTP/2.
Что такое запросы?
Для тех, кто только начинает работать с Python или хочет освежить свои знания, Requests — это простая и элегантная HTTP-библиотека для Python, созданная Кеннетом Рейтцем почти пятнадцать лет назад. Её главная цель — сделать HTTP-запросы простыми и удобными для пользователя. Хотите отправить данные? Выполните GET- или POST-запрос? Обработайте заголовки, файлы cookie или аутентификацию? Requests делает эти задачи интуитивно понятными.
Синхронность означает, что при отправке запроса программа ожидает ответа, прежде чем продолжить работу. Это приемлемо для многих приложений, но для задач, требующих множества одновременных HTTP-вызовов (например, веб-скрапинга или взаимодействия с несколькими микросервисами), такое блокирующее поведение может стать серьёзным узким местом.
Что такое HTTPX?
Согласно официальной документации, HTTPX — это
«…полнофункциональный HTTP-клиент для Python 3, который предоставляет синхронные и асинхронные API, а также поддерживает HTTP/1.1 и HTTP/2».
Он был разработан Encode (командой, стоящей за Starlette , Uvicorn и Django Rest Framework ).
Некоторые из преимуществ HTTPX включают в себя:
- Поддержка асинхронности: собственный синтаксис async/await для неблокирующих операций.
- Поддержка HTTP/2: в отличие от Requests (который изначально поддерживает HTTP/1.1), HTTPX может общаться по протоколу HTTP/2, что потенциально обеспечивает преимущества в производительности, такие как мультиплексирование.
- API, подобный Requests: он призван предоставить знакомый API для тех, кто привык к Requests, упрощая переход.
- API транспорта: более продвинутая функция, позволяющая настраивать поведение транспорта, полезна для тестирования или определенных сетевых конфигураций.
Заявления о HTTPX интригуют. API, совместимый с Requests, с возможностями async/await и потенциальным приростом производительности. Но является ли он очевидным преемником, способным сместить действующего чемпиона, или это узкоспециализированный инструмент для определённых асинхронных сценариев использования? Есть только один способ выяснить это. Давайте протестируем оба.
Настройка среды разработки
Прежде чем начать писать код, нам следует настроить отдельную среду разработки для каждого проекта. Я использую Conda, но вы можете использовать любой удобный для вас метод.
# Создаём нашу тестовую среду (для асинхронных функций рекомендуется Python 3.7+) # И активируем её (база) $ conda create -n httpx_test python=3.11 -y (база) $ conda activate httpx_test
Теперь, когда наша среда активна, давайте установим необходимые библиотеки:
(httpx_test) $ pip install requests httpx[http2] asyncio aiohttp uvicorn fastapi jupyter nest_asyncio
Я использую Jupyter для своего кода, поэтому, если вы следуете моим инструкциям, введите Jupyter Notebook в командной строке. В браузере должен открыться Jupyter Notebook. Если это не произойдёт автоматически, после команды Jupyter Notebook вы, вероятно, увидите экран с информацией. Внизу вы найдёте URL-адрес, который нужно скопировать и вставить в адресную строку браузера для запуска Jupyter Notebook.
Ваш URL-адрес будет отличаться от моего, но выглядеть он должен примерно так:-
http://127.0.0.1:8888/tree?token=3b9f7bd07b6966b41b68e2350721b2d0b6f388d248cc69da
Сравнение HTTPX и производительности запросов
Для сравнения производительности мы выполним серию HTTP-запросов GET с использованием обеих библиотек и замерим время. Сначала мы рассмотрим синхронные операции, а затем рассмотрим асинхронные возможности.
Для нашей цели мы будем использовать httpbin.org — отличный сервис для тестирования HTTP-запросов. Его можно рассматривать как инструмент тестирования и отладки для разработчиков, создающих или работающих с программным обеспечением, которое отправляет HTTP-запросы (например, веб-клиентами, API-клиентами, парсерами и т. д.). Вместо того, чтобы настраивать собственный веб-сервер для просмотра HTTP-запросов или тестирования того, как ваш клиент обрабатывает различные ответы сервера, вы можете отправлять запросы на тестовый сервер httpbin.org. Он предлагает множество конечных точек, предназначенных для возврата определённых типов ответов, что позволяет вам проверять и анализировать поведение вашего клиента.
Настройка локального сервера FastAPI
Давайте создадим простое приложение FastAPI, которое будет служить нашей асинхронной конечной точкой. Сохраните его как test_server.py:
# test_server.py from fastapi import FastAPI import asyncio app = FastAPI() @app.get(«/fast») async def read_fast(): return {«message»: «Привет от FastAPI!»} @app.get(«/slow») async def read_slow(): await asyncio.sleep(0.1) # Симулируем некоторую работу, связанную с вводом-выводом return {«message»: «Привет медленно от FastAPI!»}
Запустите этот сервер в отдельном окне терминала, введя эту команду.
uvicorn test_server:app —reload —host 127.0.0.1 —port 8000
Мы подготовили всё необходимое. Давайте приступим к примерам кода.
Пример 1 — Простой синхронный GET-запрос
Начнем с простого сценария: получение простого JSON-ответа 20 раз подряд.
import requests import httpx import time import nest_asyncio nest_asyncio.apply() URL = «https://httpbin.org/get» NUM_REQUESTS = 20 # — Запросы — start_time_requests = time.perf_counter() for _ in range(NUM_REQUESTS): response = requests.get(URL) assert response.status_code == 200 end_time_requests = time.perf_counter() time_requests = end_time_requests — start_time_requests print(f»Время выполнения (Запросы, Синхронизация): {time_requests:.4f} секунд») # — HTTPX (Клиент синхронизации) — start_time_httpx_sync = time.perf_counter() с httpx.Client() в качестве клиента: # Использование сеанса клиента является хорошей практикой для _ in range(NUM_REQUESTS): response = client.get(URL) assert response.status_code == 200 end_time_httpx_sync = time.perf_counter() time_httpx_sync = end_time_httpx_sync — start_time_httpx_sync print(f»Время выполнения (HTTPX, синхронизация): {time_httpx_sync:.4f} секунд»)
Выход.
Время выполнения (запросы, синхронизация): 22,6370 секунды Время выполнения (HTTPX, синхронизация): 11,4099 секунды
Это уже существенный прирост по сравнению с HTTPX по сравнению с Requests. В нашем тесте синхронное извлечение данных оказалось почти вдвое быстрее.
Пример 2 — Простой асинхронный GET-запрос (одиночный запрос) с использованием HTTPX
Теперь давайте протестируем асинхронные возможности HTTPX, сделав один запрос к локальному серверу FastAPI, который мы настроили ранее.
import httpx import asyncio import time LOCAL_URL_FAST = «http://127.0.0.1:8000/fast» async def fetch_with_httpx_async_single(): асинхронный с httpx.AsyncClient() в качестве клиента: response = await client.get(LOCAL_URL_FAST) assert response.status_code == 200 start_time_httpx_async = time.perf_counter() asyncio.run(fetch_with_httpx_async_single()) end_time_httpx_async = time.perf_counter() time_httpx_async_val = end_time_httpx_async — start_time_httpx_async print(f»Время выполнения (HTTPX, Async Single): {time_httpx_async_val:.4f} секунд»)
Выход.
Время выполнения (HTTPX, асинхронный одиночный): 0,0319 секунды
Это быстро, как и ожидалось для локального запроса. Этот тест в первую очередь проверяет работоспособность асинхронного механизма. Настоящий тест асинхронности связан с параллелизмом.
Пример 3 — Одновременные асинхронные запросы GET .
Именно здесь асинхронные возможности HTTPX должны проявить себя по сравнению с Requests. Мы будем выполнять 100 запросов к нашей конечной точке /slow одновременно.
import httpx import asyncio import time import requests LOCAL_URL_SLOW = «http://127.0.0.1:8001/slow» # задержка 0,1 с NUM_CONCURRENT_REQUESTS = 100 # — HTTPX (асинхронный клиент, параллельный) — async def fetch_one_httpx(client, url): response = await client.get(url) return response.status_code async def main_httpx_concurrent(): async с httpx.AsyncClient() в качестве клиента: tasks = [fetch_one_httpx(client, LOCAL_URL_SLOW) for _ in range(NUM_CONCURRENT_REQUESTS)] results = await asyncio.gather(*tasks) for status_code in results: assert status_code == 200 start_time_httpx_concurrent = time.perf_counter() asyncio.run(main_httpx_concurrent()) end_time_httpx_concurrent = time.perf_counter() time_httpx_concurrent_val = end_time_httpx_concurrent — start_time_httpx_concurrent print(f»Время выполнения (HTTPX, асинхронный параллельный к /slow): {time_httpx_concurrent_val:.4f} секунд») # — Для сравнения: запросы (синхронные, последовательные к /slow) — # Это будет медленно, демонстрируя проблему, которую решает асинхронность start_time_requests_sequential_slow = time.perf_counter() for _ in range(NUM_CONCURRENT_REQUESTS): response = requests.get(LOCAL_URL_SLOW) assert response.status_code == 200 end_time_requests_sequential_slow = time.perf_counter() time_requests_sequential_slow_val = end_time_requests_sequential_slow — start_time_requests_sequential_slow print(f»Время выполнения (запросы, синхронизация последовательных запросов с /slow): {time_requests_sequential_slow_val:.4f} секунд»)
Типичный вывод
Время выполнения (HTTPX, асинхронный параллельный в /slow): 0,1881 секунды Время выполнения (запросы, синхронный последовательный в /slow): 10,1785 секунды
Вот это совсем неплохо! HTTPX с использованием asyncio.gather выполнил 100 запросов (каждый с имитацией задержки 0,1 с) чуть более чем за секунду. Поскольку задачи привязаны к вводу-выводу, asyncio может переключаться между ними, пока они ждут ответа сервера. Общее время примерно равно времени выполнения самого длинного отдельного запроса плюс небольшая накладная сумма на управление параллельными запросами.
Для сравнения, синхронный код Requests выполнялся более 10 секунд (100 запросов * 0,1 с/запрос = 10 секунд плюс накладные расходы). Это демонстрирует мощь асинхронных операций для задач, связанных с вводом-выводом. HTTPX не просто «быстрее» в абсолютном смысле; он обеспечивает принципиально более эффективный способ обработки параллельных операций ввода-вывода.
А как насчет HTTP/2?
HTTPX поддерживает HTTP/2, если сервер также поддерживает его и установлена библиотека h2 (pip install httpx[h2]). HTTP/2 предлагает такие преимущества, как мультиплексирование (отправка нескольких запросов по одному соединению) и сжатие заголовков.
import httpx import asyncio import time # Публичный сервер с поддержкой HTTP/2 HTTP2_URL = «https://github.com» # HTTP2_URL = «https://www.cloudflare.com» # Другой вариант NUM_HTTP2_REQUESTS = 20 async def fetch_http2_info(): async with httpx.AsyncClient(http2=True) as client: # Включить HTTP/2 for _ in range(NUM_HTTP2_REQUESTS): response = await client.get(HTTP2_URL) # print(f»HTTP Version: {response.http_version}, Status: {response.status_code}») assert response.status_code == 200 assert response.http_version in [«HTTP/2», «HTTP/2.0″] # Проверить, использовался ли HTTP/2 start_time = time.perf_counter() asyncio.run(fetch_http2_info()) end_time = time.perf_counter() print(f»Время выполнения (HTTPX, асинхронное с HTTP/2): {end_time — start_time:.4f} секунд для {NUM_HTTP2_REQUESTS} запросов.»
Выход
Время выполнения (HTTPX, асинхронно с HTTP/2): 0,7927 секунды для 20 запросов.
Хотя этот тест подтверждает использование HTTP/2, количественная оценка его преимущества в скорости по сравнению с HTTP/1.1 в простом скрипте может быть довольно сложной. Преимущества HTTP/2 часто становятся более очевидными в сложных сценариях с большим количеством небольших ресурсов или при соединениях с высокой задержкой. Для многих распространённых взаимодействий с API разница может быть не столь значительной, если сервер специально не оптимизирован для активного использования возможностей HTTP/2. Тем не менее, наличие этой возможности — важная функция, ориентированная на будущее.
За пределами чистой скорости
Производительность — это ещё не всё. Опыт разработчиков, функциональность и простота использования имеют решающее значение, поэтому давайте рассмотрим некоторые из них в нашем сравнении двух библиотек.
Поддержка асинхронности/ожидания
HTTPX. Первоклассная поддержка. Это его главное отличие.
ЗАПРОСЫ. Чисто синхронные. Чтобы добиться асинхронного поведения, похожего на Requests, обычно используют библиотеки вроде aiohttp (у которой другой API) или используют Requests в исполнителе пула потоков (что добавляет сложности и не является настоящим asyncio).
Поддержка HTTP/2
HTTPX. Мы уже упоминали об этом, но, напомним, эта функция встроена.
ЗАПРОСЫ. Нет встроенной поддержки HTTP/2. Сторонние адаптеры существуют, но не так интегрированы.
Дизайн API и простота использования
HTTPX. Он специально разработан, чтобы быть максимально похожим на Requests. Если вы знакомы с Requests, HTTPX покажется вам знакомым. Вот краткое сравнение кода.
# Запросы r = requests.get('https://example.com', params={'key': 'value'}) # HTTPX (синхронный) r = httpx.get('https://example.com', params={'key': 'value'}) # HTTPX (асинхронный) асинхронный с httpx.AsyncClient() в качестве клиента:
ЗАПРОСЫ. Золотой стандарт простоты синхронных HTTP-вызовов.
Клиентские сеансы / Пул соединений
Обе библиотеки настоятельно рекомендуют использовать клиентские сеансы (requests.Session() и httpx.Client() / httpx.AsyncClient()) для повышения производительности, например, для организации пула соединений и сохранения файлов cookie. Применение обеих библиотек очень похоже.
След зависимости
ЗАПРОСЫ. Относительно лёгкий (charset_normalizer, idna, urllib3, certifi).
HTTPX. Имеет ещё несколько основных зависимостей (httpcore, sniffio, anyio, certifi, idna) и h11 для HTTP/1.1. Если добавить h2 для HTTP/2, то это уже совсем другое дело. Это понятно, учитывая более широкий набор функций.
Зрелость и сообщество
ЗАПРОСЫ. Чрезвычайно зрелое, огромное сообщество, проверенное в боях более десяти лет.
HTTPX. Более молодой, но активно разрабатываемый авторитетной командой (Encode) и быстро набирающий популярность. Он считается стабильным и готовым к использованию.
Когда выбирать HTTPX, а когда — Requests?
Учитывая всё вышесказанное, как выбрать между этими двумя вариантами? Вот что я думаю.
Выберите HTTPX, если…
- Вам нужны асинхронные операции. Это основная причина. Если ваше приложение использует множество HTTP-вызовов, связанных с вводом-выводом, HTTPX с asyncio обеспечит значительное повышение производительности и более эффективное использование ресурсов.
- Вам нужна поддержка HTTP/2. Если вы взаимодействуете с серверами, которые используют HTTP/2 для повышения производительности, HTTPX обеспечивает её по умолчанию.
- Вы начинаете новый проект и хотите обеспечить его соответствие требованиям завтрашнего дня. Современный дизайн HTTPX и асинхронные возможности делают его отличным выбором для новых приложений.
- Вам нужна одна библиотека как для синхронных, так и для асинхронных HTTP-вызовов. Это может упростить управление зависимостями и кодовой базой, если у вас смешанные потребности.
- Вам нужны расширенные функции. Например, API Transport для точного управления отправкой запросов или тестирования.
Придерживайтесь запроса, если…
- Ваше приложение полностью синхронное и имеет простые HTTP-требования. Если Requests уже хорошо справляется со своей задачей и у вас нет узких мест ввода-вывода, возможно, нет веских причин для перехода.
- Вы работаете с устаревшей кодовой базой, сильно зависящей от запросов. Миграция может быть нецелесообразной, если вам не нужны функции HTTPX.
- Минимизация зависимостей критически важна. Запросы занимают немного меньше места.
- Освоение asyncio может стать препятствием для вашей команды. Хотя HTTPX предлагает API для синхронизации, его главное преимущество заключается в асинхронных возможностях.
Краткое содержание
Моё исследование показало, что HTTPX — очень мощная библиотека. Хотя она не делает волшебным образом одиночные синхронные HTTP-вызовы значительно быстрее Requests (задержка в сети всё ещё играет решающую роль), её истинная мощь проявляется в асинхронных приложениях. При выполнении множества одновременных вызовов, связанных с вводом-выводом, HTTPX обеспечивает существенный прирост производительности и более эффективную структуру кода, что продемонстрировано в нашем тесте на параллельные процессы.
Многие утверждают, что HTTPX «лучше», но это зависит от контекста. Если «лучше» означает встроенную поддержку async/await, возможности HTTP/2 и современный API, который также учитывает потребности синхронной работы, то да, HTTPX, пожалуй, имеет преимущество для новых разработок. Requests остаётся превосходной и надёжной библиотекой для синхронных задач, и её простота по-прежнему остаётся её главным преимуществом.
Для параллельных асинхронных операций эффективная пропускная способность при использовании httpx может быть на порядок выше, чем у последовательных синхронных запросов, что кардинально меняет ситуацию.
Если вы разработчик Python, работающий с HTTP-вызовами, особенно в современных веб-приложениях, микросервисах или задачах с большими объёмами данных, то HTTPX — это не просто библиотека для изучения, это библиотека, с которой стоит начать работать. Переход от Requests к синхронному коду происходит плавно, а её общий набор функций и асинхронные возможности делают её привлекательным выбором для будущего HTTP-клиентов Python.
Источник: towardsdatascience.com



























