Библиотека requests: первые запросы

requests — самая популярная Python-библиотека для отправки HTTP-запросов.
requests превращает HTTP в одну строку кода: requests.get(url) возвращает объект ответа с кодом статуса, заголовками и телом страницы.

Стандартная библиотека Python умеет ходить в сеть через urllib, но делать это вручную неудобно. Библиотека requests стала де-факто стандартом: простой синтаксис, автоматические редиректы, удобная работа с куки и заголовками. Это первый инструмент, который скрейпер ставит в проект.

Важно: requests — внешняя библиотека, её нет в браузерном Pyodide. Поэтому код с requests в этом курсе показан как пример для запуска у тебя на компьютере, а не в нашем онлайн-раннере. Сначала установим:

pip install requests

Базовый GET-запрос выглядит так:

import requests

resp = requests.get('https://example.com')

print(resp.status_code)   # 200
print(resp.headers['Content-Type'])
print(resp.text[:200])    # первые 200 символов HTML

Заголовки, параметры и таймауты

Чтобы выглядеть как обычный браузер и не получить 403, скрейперу часто нужно передать заголовки — прежде всего User-Agent. Параметры запроса удобно передавать словарём — requests сам соберёт query-string.

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (обучающий скрейпер; +https://my-site/bot)'
}
params = {'page': 2, 'sort': 'price'}

resp = requests.get(
    'https://shop.example/catalog',
    headers=headers,
    params=params,
    timeout=10,          # не ждать ответа вечно
)
resp.raise_for_status()  # бросит исключение на 4xx/5xx
print(resp.url)          # итоговый URL с параметрами

Как работает под капотом

Под капотом requests открывает TCP-соединение, отправляет правильно оформленные HTTP-заголовки, читает ответ и оборачивает его в удобный объект Response. Свойство .text — это тело, декодированное в строку; .content — сырые байты (нужны для картинок); .json() — разбор тела как JSON, если сервер вернул API-ответ. Метод raise_for_status() превращает «плохие» коды (4xx/5xx) в исключения, чтобы ошибка не прошла незамеченной.

Сессии, куки и повторные попытки

Когда запросов к одному сайту много, отдельные requests.get неэффективны: на каждый заново устанавливается соединение. Объект requests.Session() переиспользует TCP-соединение и автоматически хранит куки между запросами — это и быстрее, и ближе к поведению браузера. Через сессию удобно один раз задать общие заголовки (session.headers.update(...)), и они применятся ко всем последующим запросам.

Сеть ненадёжна: соединение может оборваться, сервер — временно ответить 503. Промышленный скрейпер не падает от первой же ошибки, а повторяет запрос с нарастающей паузой — это называется экспоненциальный бэк-офф: подождать 1 секунду, затем 2, затем 4. Такой подход одновременно устойчив к сбоям и вежлив: получив 429 или 503, ты не продолжаешь долбить сервер, а даёшь ему передышку. Эту логику можно написать руками или взять готовый адаптер HTTPAdapter с настройкой Retry.

Частые ошибки

  • Запрос без таймаута. Без timeout скрипт может зависнуть навсегда на медленном сервере.
  • Игнорировать кодировку. Иногда resp.encoding определяется неверно — кириллица превращается в кракозябры; задай кодировку явно.
  • Не переиспользовать сессию. Для множества запросов к одному сайту лучше requests.Session() — она держит соединение и куки.

Best practices

  • Всегда указывай timeout и проверяй статус (raise_for_status()).
  • Используй Session() для серии запросов — это быстрее и сохраняет куки.
  • Передавай осмысленный User-Agent с контактом — это честно по отношению к сайту.

Не забывай про кодировку ответа. Иногда сервер не указывает её корректно, и requests неверно угадывает — кириллица превращается в нечитаемые символы. В таких случаях кодировку задают явно (resp.encoding = 'utf-8') до обращения к resp.text, либо работают с сырыми байтами resp.content и декодируют сами. Этот нюанс особенно часто всплывает на старых русскоязычных сайтах. Понимание разницы между .text (декодированная строка) и .content (байты) избавляет от целого класса загадочных «кракозябр» в собранных данных.

Полезно с самого начала логировать ход работы: какой URL запрашиваем, какой код вернулся, сколько данных извлекли. Модуль logging из стандартной библиотеки подходит идеально и не требует зависимостей. Логи бесценны, когда скрейпер падает на тысячной странице из десяти тысяч: по записям видно, на каком URL и с каким кодом ответа что-то пошло не так. Привычка логировать, а не печатать print наугад, отличает одноразовый скрипт от инструмента, которому можно доверить регулярный сбор данных.

Итог: requests — рабочая лошадка скрейпинга для статических страниц. Запомни связку get → status → text, всегда ставь таймаут и проверяй код ответа.

Проверьте себя
1. Зачем в запросе указывать параметр timeout?
AЧтобы ускорить интернет
BЧтобы скрипт не завис навсегда, если сервер не отвечает
CЧтобы обойти защиту от ботов
Dtimeout не нужен
2. Что делает метод raise_for_status()?
AВсегда возвращает 200
BБросает исключение, если код ответа 4xx или 5xx
CМеняет User-Agent
DСохраняет страницу на диск