HTTP под капотом: запросы, ответы и коды статуса

Скрейпер общается с сайтом на языке HTTP — том же, на котором говорит браузер.
HTTP (HyperText Transfer Protocol) — протокол «запрос-ответ»: клиент отправляет запрос с методом, адресом и заголовками, сервер возвращает ответ с кодом статуса, заголовками и телом.

Чтобы скрейпить осознанно, нужно понимать, что именно уходит на сервер и что приходит обратно. Когда ты открываешь страницу в браузере, под капотом происходит ровно то же, что будет делать твой скрипт: отправляется HTTP-запрос и приходит HTTP-ответ. Разница только в том, что браузер ещё и красиво рисует результат.

Анатомия запроса и ответа

Запрос состоит из метода (GET — «дай данные», POST — «прими данные»), URL (адрес ресурса), заголовков (метаданные: какой браузер, какой язык, какие куки) и иногда тела (для POST). Ответ состоит из кода статуса, заголовков и тела — обычно это и есть HTML-страница.

  КЛИЕНТ (скрейпер)                         СЕРВЕР
      │                                        │
      │  GET /catalog HTTP/1.1                 │
      │  Host: shop.example                    │
      │  User-Agent: my-bot/1.0   ---------->  │
      │                                        │  ищет страницу
      │  HTTP/1.1 200 OK                       │
      │  Content-Type: text/html               │
      │  <html>...</html>          <---------- │
      │                                        │

Коды статуса, которые встретит скрейпер

КодЗначениеЧто делать скрейперу
200OK — всё хорошоПарсить тело ответа
301/302РедиректПерейти по новому адресу (requests делает это сам)
403Доступ запрещёнВозможно, сайт распознал бота — проверь заголовки
404Не найденоСтраницы нет — пропустить
429Too Many RequestsТы шлёшь слишком часто — притормози!
500/503Ошибка/перегрузка сервераПодождать и повторить позже

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

URL сам по себе несёт структуру, которую полезно уметь разбирать. В стандартной библиотеке Python есть модуль urllib.parse — он работает прямо в браузере (Pyodide) и не требует интернета. Разберём адрес на части:

Попробуй сам ▶

from urllib.parse import urlparse, parse_qs

url = 'https://shop.example/catalog?page=2&sort=price&q=ноутбук'
p = urlparse(url)
print('схема :', p.scheme)
print('хост  :', p.netloc)
print('путь  :', p.path)
print('запрос:', p.query)

# query-string -> словарь
params = parse_qs(p.query)
for key, val in params.items():
    print(f'  {key} = {val[0]}')

Так скрейпер понимает, как менять параметры пагинации (page=2, page=3) или поисковый запрос, не открывая сайт руками. Это основа обхода каталогов по страницам.

Заголовки запроса: что именно ты сообщаешь серверу

Каждый запрос несёт заголовки — пары «имя: значение», описывающие отправителя. User-Agent сообщает, какой клиент пришёл; Accept — какие форматы ответа подходят; Accept-Language — предпочитаемый язык; Referer — с какой страницы был переход; Cookie — сохранённое состояние сессии. Браузер заполняет всё это автоматически, а вот requests по умолчанию отправляет минимум — и именно поэтому «голый» запрос часто получает 403: серверу он выглядит подозрительно безликим.

Ответ тоже несёт заголовки, и их полезно читать. Content-Type подсказывает, HTML перед тобой или JSON. Content-Encoding говорит о сжатии (gzip). Set-Cookie выдаёт куки для следующих запросов. Retry-After в ответе 429 прямо указывает, через сколько секунд можно повторить — игнорировать эту подсказку и значит вести себя невежливо. Понимание заголовков превращает скрейпер из «слепого» клиента в осознанного участника HTTP-диалога.

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

  • Не смотреть на код ответа. Получив 403 или 429, наивный скрейпер парсит страницу-заглушку и сохраняет мусор.
  • Игнорировать 429. Это прямой сигнал «ты слишком частишь» — продолжать долбить нельзя.
  • Думать, что заголовки не важны. Часто именно отсутствие нормального User-Agent приводит к 403.

Best practices

  • Всегда проверяй response.status_code перед парсингом.
  • На 429 и 503 делай паузу и повторяй с увеличивающейся задержкой (экспоненциальный бэк-офф).
  • Отправляй честный, описательный User-Agent — об этом в разделе про этику.

Стоит запомнить и про идемпотентность методов. GET по смыслу безопасен и не должен менять состояние сервера — именно его использует скрейпер, когда просто читает данные. POST, PUT, DELETE меняют данные, и применять их при сборе информации почти никогда не нужно. Это ещё один этический ориентир: читающий скрейпер ограничивается GET и не трогает то, что способно изменить чужую систему. Чёткое понимание семантики методов удерживает тебя в безопасной зоне «только чтение публичного».

Итог: HTTP — это диалог «запрос-ответ». Скрейпер обязан читать код статуса и реагировать на 403/429/503. Разбор URL через urllib.parse — первый практический навык сбора данных.

Проверьте себя
1. Что означает код ответа 429?
AСтраница не найдена
BСервер сломался
CToo Many Requests — клиент шлёт запросы слишком часто
DДоступ запрещён навсегда
2. Зачем скрейперу разбирать URL через urllib.parse?
AЧтобы ускорить интернет
BЧтобы понять структуру адреса и менять параметры (например, номер страницы) при обходе каталога
CЧтобы зашифровать запрос
Durllib.parse не нужен для скрейпинга