Веб-скрапинг с httpx в python

Веб-скрапинг с httpx в python открывает новые горизонты для сбора информации из сети. В отличие от классической библиотеки `requests`, `httpx` изначально спроектирован для поддержки асинхронных операций и протокола HTTP/2, что делает его идеальным инструментом для современных, высокопроизводительных задач. Если вам нужно быстро и эффективно извлекать контент с десятков или сотен страниц, асинхронность становится не роскошью, а необходимостью. Этот материал проведет вас от базовых концепций до практических примеров, которые помогут автоматизировать сбор данных.

Почему именно httpx?

Выбор инструмента часто определяет успех всего проекта. В мире Python библиотека `requests` долгое время была золотым стандартом для HTTP-запросов. Однако веб меняется, и `httpx` предлагает ответы на новые вызовы. Его ключевые преимущества:

  • Асинхронность "из коробки": Поддержка `async/await` позволяет выполнять множество запросов одновременно, не блокируя основной поток выполнения. Это кардинально ускоряет процесс скрапинга.
  • Поддержка HTTP/2: Многие современные серверы используют HTTP/2, который эффективнее мультиплексирует запросы по одному соединению, снижая задержки.
  • Совместимый API: Переход с `requests` на `httpx` максимально прост, так как API во многом идентичен. Вы сможете быстро адаптировать существующий код.
  • Таймауты и управление подключениями: Гибкая настройка таймаутов на разных уровнях (подключение, чтение, запись) и эффективное управление пулом соединений.

Эти особенности делают `httpx` мощным решением для задач, где производительность имеет решающее значение. Вы не просто отправляете запросы, вы управляете ими на новом уровне эффективности.

Установка и первый синхронный запрос

Начать работу с библиотекой очень просто. Установка выполняется через стандартный менеджер пакетов `pip`. Рекомендуется устанавливать с дополнительными зависимостями для полной поддержки HTTP/2 и других возможностей.

pip install httpx[http2]

После установки можно сразу сделать первый запрос. Если вы знакомы с `requests`, следующий код покажется вам родным. Создадим синхронный GET-запрос к сайту-примеру.

import httpx

def fetch_sync_data(url):
    try:
        response = httpx.get(url)
        response.raise_for_status()  # Проверяет, был ли ответ успешным (код 2xx)
        print(f"Статус-код: {response.status_code}")
        print(f"Заголовки: {response.headers['Content-Type']}")
        return response.text
    except httpx.HTTPStatusError as exc:
        print(f"Ошибка HTTP: {exc.response.status_code} для адреса {exc.request.url}")
    except httpx.RequestError as exc:
        print(f"Произошла ошибка при запросе к {exc.request.url}")

if __name__ == "__main__":
    data = fetch_sync_data("https://httpbin.org/get")
    if data:
        print("Запрос выполнен успешно!")

Этот код отправляет простой GET-запрос, проверяет статус ответа и выводит базовую информацию. Метод `raise_for_status()` удобен для автоматической проверки ошибок, связанных с HTTP-статусами (например, 404 Not Found или 500 Internal Server Error).

Магия асинхронности: ускоряем сбор данных

Главное преимущество `httpx` — асинхронные операции. Они позволяют программе не ждать завершения одного долгого сетевого запроса, а заниматься другими задачами в это время. Для веб-скрапинга это означает возможность одновременно запрашивать множество URL.

Асинхронный подход — это не про ускорение одного запроса, а про эффективное управление множеством одновременных операций. Вместо последовательного ожидания ответа от каждого сервера, мы отправляем запросы всем сразу и обрабатываем ответы по мере их поступления.

Для использования асинхронных возможностей нам понадобится `asyncio` — встроенная в Python библиотека для написания конкурентного кода. Посмотрим, как выглядит асинхронный запрос.

import asyncio
import httpx

async def fetch_async_data(client, url):
    try:
        response = await client.get(url, timeout=10.0)
        response.raise_for_status()
        print(f"Получены данные с {url}, статус: {response.status_code}")
        return response.text
    except httpx.HTTPStatusError as exc:
        print(f"Ошибка HTTP: {exc.response.status_code} для {exc.request.url}")
    except httpx.RequestError as exc:
        print(f"Ошибка при запросе к {exc.request.url}")
    return None

async def main():
    urls = [
        "https://httpbin.org/delay/1",
        "https://httpbin.org/delay/2",
        "https://httpbin.org/delay/1",
    ]
    async with httpx.AsyncClient() as client:
        tasks = [fetch_async_data(client, url) for url in urls]
        results = await asyncio.gather(*tasks)
    
    for i, result in enumerate(results):
        if result:
            print(f"Результат {i+1} получен, размер: {len(result)} байт")

if __name__ == "__main__":
    asyncio.run(main())

В этом примере мы используем `httpx.AsyncClient()` — асинхронный клиент, который управляет пулом соединений. Функция `asyncio.gather` запускает все наши задачи (запросы) конкурентно. Общее время выполнения такого скрипта будет примерно равно времени самого долгого запроса (в данном случае, 2 секунды), а не сумме всех задержек (1 + 2 + 1 = 4 секунды), как было бы при синхронном подходе.

Настройка запросов для успешного скрапинга

Многие веб-ресурсы применяют базовые механизмы защиты от ботов. Чтобы ваш скрапер был более похож на обычного пользователя, необходимо настраивать запросы. Чаще всего это касается заголовков (headers).

Имитация браузера через User-Agent

Заголовок `User-Agent` сообщает серверу, какой браузер и операционная система используются. Отсутствие этого заголовка или стандартное значение от `httpx` может стать причиной блокировки.

  1. Найдите строку своего User-Agent в Google по запросу "my user agent".
  2. Добавьте ее в заголовки при создании клиента или отдельного запроса.
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}

async with httpx.AsyncClient(headers=headers) as client:
    response = await client.get("https://example.com")

Теперь все запросы, сделанные через этот клиент, будут содержать указанный `User-Agent`. Также можно добавлять другие заголовки, например `Accept-Language` или `Referer`, для большей маскировки.

Работа с сессиями и обработка данных

При скрапинге сайтов, требующих авторизации или сохраняющих состояние между страницами (например, корзина в интернет-магазине), необходимо использовать сессии. Объект клиента (`Client` или `AsyncClient`) как раз и выполняет роль сессии: он автоматически управляет файлами cookie и поддерживает открытые TCP-соединения, что ускоряет последующие запросы к тому же хосту.

После получения HTML-содержимого страницы его нужно обработать — извлечь нужную информацию. `httpx` отвечает только за получение данных, а для их разбора (парсинга) используются другие библиотеки. Самые популярные из них:

  • Beautiful Soup 4: Отличный выбор для новичков. Прощает ошибки в HTML-разметке и имеет простой и понятный API для поиска тегов.
  • lxml: Очень быстрая и мощная библиотека, основанная на парсерах C. Поддерживает XPath и CSS-селекторы для навигации по документу.
  • parsel: Библиотека, на которой построен скрапинговый фреймворк Scrapy. Она объединяет мощь XPath и CSS-селекторов в удобном интерфейсе.

Пример интеграции `httpx` и `Beautiful Soup` для извлечения заголовка страницы:

from bs4 import BeautifulSoup
import httpx

def get_page_title(url):
    try:
        response = httpx.get(url)
        response.raise_for_status()
        soup = BeautifulSoup(response.text, "html.parser")
        title = soup.find("title").text
        return title
    except Exception as e:
        print(f"Не удалось получить заголовок: {e}")
        return None

title = get_page_title("https://www.python.org")
if title:
    print(f"Заголовок страницы: {title}")

Этические соображения

Веб-скрапинг находится в "серой" юридической зоне. Важно подходить к процессу ответственно, чтобы не навредить сайту-источнику и не нарушить его правила. Всегда проверяйте файл `robots.txt` (например, `https://example.com/robots.txt`), где владельцы сайтов указывают, какие разделы можно и нельзя сканировать ботам. Уважайте эти правила.

Основные принципы этичного скрапинга:

  • Не создавайте чрезмерную нагрузку: Делайте паузы между запросами (`await asyncio.sleep(1)`), чтобы не перегружать сервер.
  • Идентифицируйте себя: Используйте осмысленный `User-Agent`, который указывает на цель вашего скрапера.
  • Используйте API, если он есть: Многие сервисы предоставляют публичный API для доступа к данным. Это всегда предпочтительный способ.
  • Кэшируйте результаты: Не скачивайте одну и ту же страницу многократно. Сохраняйте полученные данные локально.

Использование `httpx` для сбора данных — это современный и эффективный подход. Асинхронная природа библиотеки позволяет создавать быстрые и масштабируемые решения для извлечения информации из веба, делая рутинные задачи автоматизированными и точными.