Вернуться в блог

Python Requests headers: заголовки для GET, POST и User-Agent

Marcus Bennett

28 авг. 2026 г. · Руководства · 9 мин. чтения

В requests HTTP-заголовки передают через словарь в аргументе headers методов requests.get(), requests.post() и других вызовов. Общие настройки для серии запросов удобнее задать в Session через session.headers.update(). Ниже разберем рабочие примеры для GET и POST, настройку User-Agent, приоритеты заголовков, диагностику ответа 403 и подключение прокси для разрешенных задач.

Материал рассчитан на разработчиков API-клиентов, тестовых скриптов и систем сбора открытых данных, работающих в рамках правил целевого ресурса. Примеры используют локальный сервер: их можно повторить без отправки данных на сторонний сайт.

Коротко

Для одного запроса передайте словарь в headers, например requests.get(url, headers={"Accept": "application/json"}). Для постоянных значений используйте session.headers.update(...). Проверяйте фактически подготовленный запрос через response.request.headers, а заголовки ответа читайте из response.headers.

Что такое headers в Python Requests

HTTP-заголовки представляют собой пары «имя-значение», сопровождающие запрос или ответ. В них передают формат данных, сведения о клиенте, параметры кеширования и данные аутентификации. В Python Requests исходящие заголовки обычно задают словарем в headers=, а ответ сервера доступен в response.headers.

На практике чаще всего нужны Accept, Content-Type, Authorization и User-Agent. Корректные значения помогают серверу правильно обработать вызов и делают ошибки API понятнее. Для одиночного вызова достаточно headers=; для нескольких связанных запросов используйте requests.Session.

Подготовка окружения

Установка зависимостей

Создайте виртуальное окружение и установите проверенные версии пакетов:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install requests==2.34.2 PySocks==1.7.1

PySocks нужен только для примера с SOCKS-прокси. Для остальных фрагментов достаточно Requests.

Компонент Версия Назначение
Python 3.14.0 Запуск примеров
Requests 2.34.2 Отправка и просмотр HTTP-запросов
PySocks 1.7.1 Поддержка адресов вида socks5h://
Тестовая цель Локальный HTTP echo-сервер Воспроизводимая проверка без внешнего сервиса

python requests headers blog figure 1

Рисунок 1. Перед запуском примеров проверьте версии Python и Requests.

Локальный echo-сервер

Сохраните следующий код как code/local_echo_server.py. Он принимает запросы на 127.0.0.1, возвращает полученные заголовки и тело, а для пути /blocked намеренно отвечает 403.

import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer


class Handler(BaseHTTPRequestHandler):
    def reply(self, status=200):
        length = int(self.headers.get("Content-Length", "0"))
        raw_body = self.rfile.read(length) if length else b""
        parsed_json = None
        body_text = None
        if raw_body:
            body_text = raw_body.decode("utf-8", errors="replace")
            try:
                parsed_json = json.loads(raw_body)
            except json.JSONDecodeError:
                pass
        payload = {
            "method": self.command,
            "path": self.path,
            "headers": dict(self.headers),
            "json": parsed_json,
            "body_text": body_text,
        }
        body = json.dumps(payload, indent=2).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def do_GET(self):
        self.reply(403 if self.path == "/blocked" else 200)

    def do_POST(self):
        self.reply()

    def log_message(self, format, *args):
        return


server = ThreadingHTTPServer(("127.0.0.1", 8765), Handler)
print("Local test server: http://127.0.0.1:8765", flush=True)
print("Press Control-C to stop.", flush=True)
try:
    server.serve_forever()
except KeyboardInterrupt:
    pass
finally:
    server.server_close()

В первом окне терминала запустите сервер и не закрывайте его до окончания тестов:

python3 code/local_echo_server.py

python requests headers blog figure 2

Рисунок 2. Локальный сервер слушает порт 8765 и готов принимать клиентские запросы.

Как задать заголовки в Python Requests

Создаем словарь headers

Заголовок несет метаданные до тела запроса: допустимый формат ответа, тип отправляемого тела, параметры аутентификации или сведения о программе. В Requests это обычный словарь:

import requests

url = "http://127.0.0.1:8765/headers"
headers = {
    "User-Agent": "inventory-client/2.1",
    "Accept": "application/json",
}
response = requests.get(url, headers=headers, timeout=(2, 5))
response.raise_for_status()
print(response.status_code)

Имена HTTP-заголовков регистронезависимы, но единый стиль записи облегчает чтение кода. Значения должны быть строками. Content-Length вручную лучше не указывать: Requests вычислит его по закодированному телу. Помните и о специальных аргументах: auth= или учетные данные в URL прокси могут иметь приоритет над значениями из headers.

Headers для GET-запроса

Ниже минимальный, но полноценный вариант GET-запроса с заголовками:

import requests

url = "http://127.0.0.1:8765/headers"
headers = {
    "User-Agent": "inventory-client/2.1 (+https://rola-ip.co/)",
    "Accept": "application/json",
    "X-Trace-ID": "items-read-001",
}
response = requests.get(url, headers=headers, timeout=(2, 5))
response.raise_for_status()
received = response.json()
print("Status:", response.status_code)
print("Method:", received["method"])
print("User-Agent:", received["headers"]["User-Agent"])
print("X-Trace-ID:", received["headers"]["X-Trace-ID"])

Accept сообщает о предпочтительном формате ответа, но не заставляет сервер вернуть JSON. Вызывайте response.json() после успешного ответа и, для нестабильного API, после проверки Content-Type.

Задавайте timeout всегда. Первое число ограничивает ожидание подключения, второе – паузу между порциями данных ответа. Эта пара не означает, что вся загрузка завершится за их сумму.

python requests headers blog figure 3

Рисунок 3. Echo-сервер получил заголовки конкретного запроса и вернул HTTP 200.

Как установить User-Agent

По умолчанию Requests использует User-Agent библиотеки, например python-requests/2.34.2. Если API просит идентифицировать клиент, задайте понятное имя приложения:

import requests

response = requests.get(
    "http://127.0.0.1:8765/headers",
    headers={"User-Agent": "inventory-client/2.1 (+https://rola-ip.co/)"},
    timeout=(2, 5),
)
response.raise_for_status()
print(response.json()["headers"]["User-Agent"])

Название продукта, версия и страница с контактами полезнее оператору API, чем строка браузера, скопированная из другого клиента. Если сервис документирует свой формат User-Agent, соблюдайте именно его.

Подмена User-Agent не превращает Requests в Chrome: библиотека не исполняет JavaScript, не воспроизводит TLS-профиль браузера, client hints и сценарии аутентификации. Поэтому User-Agent – средство идентификации, а не универсальное решение для 403 Forbidden.

Headers в POST: JSON, формы и Content-Type

POST с JSON-телом

Для JSON API передавайте объект Python через json=:

import requests

url = "http://127.0.0.1:8765/items"
headers = {
    "User-Agent": "inventory-client/2.1 (+https://rola-ip.co/)",
    "Accept": "application/json",
    "X-Trace-ID": "item-create-001",
}
payload = {"name": "monitor", "quantity": 2}
response = requests.post(url, headers=headers, json=payload, timeout=(2, 5))
response.raise_for_status()
received = response.json()
print("Status:", response.status_code)
print("Method:", received["method"])
print("User-Agent:", received["headers"]["User-Agent"])
print("Content-Type:", received["headers"]["Content-Type"])
print("JSON received:", received["json"])

json=payload сериализует объект и добавляет подходящий JSON Content-Type. Не вызывайте сначала json.dumps(), а затем не передавайте строку в json=: сервер получит JSON-строку вместо исходной структуры.

data= выбирают, когда контракт API требует полей формы или уже закодированного тела. Верный вариант определяется контрактом конечной точки, а не только методом HTTP.

Сервер ожидает Аргумент Requests Обычный Content-Type
JSON-объект json={"key": "value"} application/json
Поля HTML-формы data={"key": "value"} application/x-www-form-urlencoded
Байты или текст data=encoded_body Укажите согласно контракту API
Загрузку файла files={...} multipart/form-data; boundary=...

python requests headers blog figure 4

Рисунок 4. POST-запрос передал User-Agent, заголовок трассировки, Content-Type и JSON-объект.

Session и проверка фактических headers

Повторное использование заголовков с Session

requests.Session хранит заголовки и cookies между вызовами, а также переиспользует соединения с одним хостом. Это удобно для небольшого API-клиента или последовательной разрешенной обработки страниц:

import requests

with requests.Session() as session:
    session.headers.update({
        "User-Agent": "inventory-client/2.1",
        "Accept": "application/json",
    })
    response = session.get(
        "http://127.0.0.1:8765/headers",
        headers={"X-Trace-ID": "session-001"},
        timeout=(2, 5),
    )
    response.raise_for_status()
    received = response.json()
    print("Status:", response.status_code)
    print("User-Agent:", received["headers"]["User-Agent"])
    print("X-Trace-ID:", received["headers"]["X-Trace-ID"])

Requests объединяет заголовки конкретного запроса со значениями Session. При совпадении ключа побеждает значение вызова. Не передавайте изменяемый Session нескольким потокам без синхронизации: cookies и другое состояние станут непредсказуемыми.

python requests headers blog figure 5

Рисунок 5. User-Agent из Session и X-Trace-ID конкретного запроса попали в одно сообщение.

Как посмотреть отправленные и полученные заголовки

Проверяйте подготовленный запрос вместо предположения, что исходный словарь ушел без изменений:

import requests

response = requests.get(
    "http://127.0.0.1:8765/headers",
    headers={"User-Agent": "inspection-client/1.0"},
    timeout=(2, 5),
)
response.raise_for_status()
print("Method:", response.request.method)
print("URL:", response.request.url)
print("User-Agent sent:", response.request.headers["User-Agent"])
print("Response Content-Type:", response.headers["Content-Type"])
Выражение Что содержит
response.request.headers Заголовки, отправленные клиентом
response.headers Заголовки, возвращенные сервером
session.headers Значения по умолчанию для Session
response.history Предыдущие ответы при редиректах

Не выводите в production-лог все заголовки целиком. В Authorization, Cookie, Proxy-Authorization, API-ключах и пользовательских токенах могут оказаться секреты. Логируйте только безопасные поля или маскируйте их значения.

python requests headers blog figure 6

Рисунок 6. Скрипт отдельно показывает headers, отправленные Requests, и headers ответа сервера.

Приоритеты и редиректы

Итоговый запрос формируется из нескольких источников. Важны три правила:

  • Если схема поддерживается библиотекой, используйте auth= вместо ручной сборки Authorization. Данные из .netrc и auth= могут переопределить одноименный ключ в headers.
  • Учетные данные прокси передавайте в URL прокси или по документированному провайдером способу. Requests может заменить заданный вручную Proxy-Authorization.
  • Не задавайте Content-Length: если длина тела известна, Requests рассчитает или заменит значение сам.

При редиректе на другой хост Requests удаляет Authorization из соображений безопасности. Это не значит, что первоначальный словарь проигнорирован: при отладке сверяйте response.history и подготовленные headers на финальном URL.

Ошибки headers: диагностика до изменения кода

Что проверить при 400, 401, 403, 415 и 429

Сначала соберите статус, тело ответа, финальный URL, историю редиректов и безопасную часть response.request.headers. Добавление набора «браузерных» заголовков к каждому неудачному запросу обычно маскирует причину.

Симптом Вероятная причина Как проверить Практическое действие
400 Bad Request Некорректный синтаксис, заголовок или структура тела Прочитать ошибку API и сверить схему Исправить поле или кодировку
401 Unauthorized Нет, истекли или были переопределены учетные данные Проверить схему auth и безопасные метаданные токена Обновить данные; при поддержке использовать auth=
403 Forbidden Права, политика, лимит, репутация IP или контроль автоматизации Сверить права, лимиты, ответ и разрешенный сетевой путь Запросить доступ, снизить частоту или использовать согласованный маршрут
406 Not Acceptable Неподдерживаемый Accept Проверить доступные форматы ответа Передать документированный media type
415 Unsupported Media Type Тело не соответствует Content-Type Проверить подготовленные тело и headers вместе Правильно выбрать json=, data= или files=
429 Too Many Requests Превышен rate limit Проверить Retry-After и лимиты провайдера Уменьшить параллелизм, сделать backoff с jitter
Таймаут соединения/чтения Сеть, прокси или медленный upstream Раздельно проверить DNS, прямой путь и прокси Ограничить таймауты и повторять лишь безопасные операции

При 403 сначала проверьте URL, авторизацию, правила доступа и частоту запросов. Свой User-Agent может быть нужен для идентификации клиента, но сам по себе не дает права доступа. Случайно добавленные Referer, скопированные cookies или Sec-CH-UA-* нередко делают запрос менее согласованным.

Повторные попытки тоже требуют контекста. GET часто можно повторить, а автоматический повтор POST способен создать дубль, если API не поддерживает ключ идемпотентности. Учитывайте Retry-After и ограничивайте экспоненциальную задержку.

Следующий код воспроизводит контролируемый 403, не вызывая raise_for_status() раньше времени:

import requests

response = requests.get("http://127.0.0.1:8765/blocked", timeout=(2, 5))
print("Status:", response.status_code)
print("URL:", response.request.url)
print("User-Agent sent:", response.request.headers["User-Agent"])
if response.status_code == 403:
    print("Diagnosis: controlled 403 reproduced; inspect policy and permissions.")

python requests headers blog figure 7

Рисунок 7. Демонстрация намеренно получает 403 и показывает подготовленный User-Agent до дальнейшей диагностики.

Прокси в Requests: отделяйте сетевой маршрут от headers

Если разрешенный запрос работает из одной сети, но постоянно отклоняется с общего серверного IP, причина может быть в репутации адреса, геолокации или лимите на IP, а не в словаре headers. Аутентифицированный прокси меняет сетевой маршрут, тогда как заголовки описывают само HTTP-сообщение.

Rola IP предлагает резидентские прокси, динамические дата-центровые прокси и мобильные прокси. Дата-центровый маршрут может подойти для стабильных, чувствительных к скорости API-вызовов; резидентский и мобильный выбирают с учетом разрешенных правил доступа, требуемой географии и объема работы. Для сценариев на Python используйте инструкцию по подключению прокси в Python.

В разделе Proxy Setup панели Rola IP выберите регион, протокол, способ аутентификации и аккаунт. Хост, порт, логин и пароль храните в переменных окружения или менеджере секретов, а не в исходном коде. Порядок первичного подключения описан в руководстве по быстрому запуску прокси.

python requests headers blog figure 8

Рисунок 8. В Proxy Setup доступны хост, порт и параметры аутентификации для подключения.

Если нескольким вызовам нужен один выходной IP, выбирайте закрепленную сессию; когда отдельным допустимым запросам требуются разные выходы – ротацию на запрос. Параметры локации направляют трафик через нужную страну или регион. Новый IP не исправит неверный Content-Type или аутентификацию, как и другой User-Agent не устранит ограничение по репутации IP или географии.

Прокси не заменяет авторизацию, не отменяет условия использования, требования privacy, robots-директивы там, где они применимы, и rate limits. Он также не исполняет JavaScript. Браузерную автоматизацию применяют только когда страница действительно требует браузерного поведения и такой сценарий разрешен.

Рекомендации для Python Requests headers

  • Указывайте правдивый и конкретный User-Agent, когда сервис просит идентифицировать клиента.
  • Согласуйте Accept и способ кодирования тела с документацией API.
  • Для JSON используйте json=, а не ручную сборку тела и длины.
  • Ограничивайте время и подключения, и чтения.
  • Перед raise_for_status() сохраните нужное для диагностики тело ошибки.
  • При отладке проверяйте response.request.headers, скрывая секреты.
  • Используйте Session для связанных последовательных запросов.
  • Повторяйте только подходящие методы, соблюдайте Retry-After и ограничивайте backoff.
  • Храните токены и данные прокси вне исходного кода.
  • Соблюдайте правила доступа, конфиденциальности и опубликованные лимиты целевого ресурса.

Итоги

Для разового вызова передавайте словарь headers, а общие настройки выносите в Session. Дайте Requests самостоятельно сериализовать JSON и рассчитать заголовки, связанные с телом, затем проверяйте подготовленный запрос при неожиданном результате. Когда разрешенная задача упирается в репутацию сети или географию, а не в headers, проблему следует рассматривать на уровне сетевого маршрута. В любом случае нужны таймауты, контролируемая частота запросов и аккуратное хранение секретов.

Часто задаваемые вопросы

Готовы начать сбор данных в большом масштабе?

Попробовать бесплатно