Механизмы сохранения состояния в HTTP-клиентах: объект Session библиотеки requests

Раздел: Сетевое программирование -> HTTP

Объект Session в библиотеке requests: поддержка куки и заголовков

Библиотека requests предоставляет класс Session, который позволяет сохранять определённые параметры между несколькими запросами. Это избавляет от необходимости передавать куки, заголовки или параметры аутентификации в каждом запросе вручную.

Создание сессии и сохранение куки с заголовками

Типичная задача - поддерживать авторизацию на сайте, которая передаётся через cookie. Сессия автоматически запоминает все куки, полученные от сервера, и отправляет их в последующих запросах. Также заголовки можно установить один раз через атрибут session.headers.

import requests

# Создание объекта сессии
session = requests.Session()

# Установка общего заголовка (например, User-Agent)
session.headers.update({
    'User-Agent': 'Mozilla/5.0 (compatible; MyApp/1.0)'
})

# Первый запрос - обычно вход на сайт
login_data = {'username': 'user', 'password': 'pass'}
login_response = session.post('https://httpbin.org/post', data=login_data)
# Куки, установленные сервером, сохранятся в session.cookies

# Второй запрос - сессия автоматически подставит куки
profile_response = session.get('https://httpbin.org/cookies')
print(profile_response.text)

Python requests get (get-запрос через requests в python)

Код не требует явного манипулирования куками - всё происходит автоматически. В ответе на второй запрос будут видны сохранённые куки.

Типичная ошибка:

Забыть обновлять заголовки при смене окружения. Например, если сессия создаётся один раз в модуле, а потом её случайно перезаписывают новым объектом Session - заголовки теряются. Решение: проверять, что используется один и тот же объект, или применять фабрику сессий.

Как передать куки вручную, не создавая сессию?

Иногда требуется отправить один запрос с определёнными куками без сохранения состояния. В requests это делается через параметр cookies.

import requests

# Словарь с куками
custom_cookies = {'session_id': 'abc123'}
response = requests.get('https://httpbin.org/cookies', cookies=custom_cookies)
print(response.text)  # сервер увидит session_id

Get html python (получение html-содержимого через http в python)

Параметр cookies принимает словарь или объект RequestsCookieJar. Этот способ не подходит, если нужно выполнить несколько последовательных запросов - куки не сохраняются автоматически.

Проблема:

Если сервер устанавливает новые куки в ответе, они не запоминаются для следующего запроса. Для многошаговых сценариев лучше использовать Session.

Как задать заголовки для одного запроса без влияния на сессию?

Сессия позволяет установить базовые заголовки, но иногда нужно переопределить их для конкретного запроса. Для этого используется параметр headers в вызове метода.

import requests

session = requests.Session()
session.headers.update({'Accept-Language': 'en-US'})

# Переопределяем Accept-Language только для этого запроса
response = session.get('https://httpbin.org/headers', headers={'Accept-Language': 'fr-FR'})
print(response.json()['headers']['Accept-Language'])  # 'fr-FR'

Url запрос python (работа с url в python)

Обратите внимание: заголовки из параметра headers сливаются с заголовками сессии. Но если в параметре указан тот же ключ, что и в сессии, параметр имеет приоритет.

Типичная ошибка:

Полная замена всех заголовков из-за неправильного понимания - на самом деле выполняется объединение, а не замена. Если нужно полностью исключить заголовки сессии, следует создать временный объект сессии без заголовков.

Как сохранить куки между перезапусками программы?

Объект Session хранит куки только во время выполнения. Чтобы сохранить их между разными запусками, нужно сериализовать session.cookies в файл.

import requests
import pickle

# Сохранение
session = requests.Session()
session.get('https://httpbin.org/cookies/set?name=value')  # получаем куки
with open('cookies.pkl', 'wb') as f:
    pickle.dump(session.cookies, f)

# Загрузка
with open('cookies.pkl', 'rb') as f:
    loaded_cookies = pickle.load(f)
new_session = requests.Session()
new_session.cookies.update(loaded_cookies)
response = new_session.get('https://httpbin.org/cookies')
print(response.text)  # куки восстановлены

Python urllib request (отправка запросов с помощью urllib.request в python)

Это полезно для долгоживущих сессий (например, парсинг с авторизацией), чтобы не логиниться при каждом запуске. Однако срок действия кук может истечь, поэтому нужна обработка ошибок.

Проблема:

Куки могут быть невалидными (истекли или заблокированы). Лучше дополнительно проверять статус ответа после загрузки и при необходимости повторно аутентифицироваться.

Как настроить базовую HTTP-аутентификацию через сессию?

Сессия может хранить объект аутентификации и передавать его во все запросы.

import requests
from requests.auth import HTTPBasicAuth

session = requests.Session()
session.auth = HTTPBasicAuth('user', 'pass')

# Запрос автоматически получит заголовок Authorization
response = session.get('https://httpbin.org/basic-auth/user/pass')
print(response.status_code)  # 200

# Можно также установить auth как кортеж
session.auth = ('user2', 'pass2')

Files upload python (загрузка файлов на сервер с помощью python (requests, flask))

Это удобно для REST API, где требуется аутентификация для каждого вызова.

Ошибка:

Если для разных хостов требуется разная аутентификация, одна сессия не подойдёт - нужно создавать отдельные сессии или временно переопределять auth в параметрах запроса.

Как управлять редиректами в сессии?

По умолчанию requests автоматически следует редиректам (код 3xx). Сессия сохраняет куки, которые могут меняться при перенаправлениях. Иногда требуется отключить редиректы или получить их историю.

import requests

session = requests.Session()
# Отключить автоматическое следование
response = session.get('https://httpbin.org/redirect/3', allow_redirects=False)
print(response.status_code)  # 302
print(response.headers['Location'])  # путь для ручного перехода

# Получить историю редиректов
response2 = session.get('https://httpbin.org/redirect/3', allow_redirects=True)
print(len(response2.history))  # 3

Python requests method (методы http-запросов в python (get, post, put, delete) с requests)

При отключении редиректов куки с промежуточных ответов не сохраняются. Для полного контроля лучше включить редиректы, а потом анализировать response.history.

Проблема:

Бесконечные редиректы могут привести к зависанию. Следует установить таймаут или лимит на количество редиректов (параметр max_redirects в адаптере транспорта).

Как обрабатывать CSRF-токены при работе с формами?

Многие сайты требуют CSRF-токен, который передаётся в куки и в теле POST-запроса. Сессия упрощает этот сценарий: сначала GET-запрос для получения куки с токеном, затем извлечение токена из HTML, и POST с этим токеном.

import requests
from bs4 import BeautifulSoup

session = requests.Session()
# Получаем страницу с формой
login_page = session.get('https://example.com/login')
soup = BeautifulSoup(login_page.text, 'html.parser')
csrf_token = soup.find('input', {'name': 'csrf_token'}).get('value')

# Отправляем POST с токеном
login_data = {
    'username': 'user',
    'password': 'pass',
    'csrf_token': csrf_token
}
response = session.post('https://example.com/login', data=login_data)
# Куки обновились, сессия готова к дальнейшим запросам

Сессия автоматически отправит куки, в которых обычно содержится вторая часть CSRF-защиты (например, csrftoken в куки).

Типичная ошибка:

Несоответствие между куки-токеном и телом запроса. Многие фреймворки ожидают, что куки с CSRF-токеном будут отправлены вместе с POST. Сессия делает это автоматически, но если токен извлекается неправильно (например, из другого поля), сервер вернёт ошибку 403. Рекомендуется проверять содержимое ответа на наличие сообщения об ошибке.

Сессия - гибкий инструмент, который можно комбинировать с адаптерами, пулом соединений, повторными попытками и таймаутами. Подробные примеры с расширенными возможностями приведены в следующем разделе.

- Python post file (отправка файла через post-запрос (requests.post(file)) в python)
- Python requests параметры (передача параметров в get/post запросах requests в python)
- Request session python (использование session в библиотеке requests (куки, заголовки) в python)

Расширенные примеры использования Session в реальных сценариях

Пример 1. Автоматическое обновление токена аутентификации

Некоторые API используют токены с ограниченным сроком жизни. После истечения токена нужно отправить запрос на обновление. Организуем это внутри сессии через перехват ошибок.

Пример
import requests

class RefreshingSession:
    def __init__(self, auth_url, credentials):
        self.session = requests.Session()
        self.auth_url = auth_url
        self.credentials = credentials
        self._get_token()
    
    def _get_token(self):
        resp = self.session.post(self.auth_url, json=self.credentials)
        resp.raise_for_status()
        token = resp.json()['access_token']
        self.session.headers.update({'Authorization': f'Bearer {token}'})
    
    def request(self, method, url, **kwargs):
        r = self.session.request(method, url, **kwargs)
        if r.status_code == 401:
            # Токен истёк, обновляем
            self._get_token()
            r = self.session.request(method, url, **kwargs)
        return r

# Использование:
client = RefreshingSession('https://api.example.com/auth', {'client_id': '...', 'client_secret': '...'})
response = client.request('GET', 'https://api.example.com/data')
print(response.text)
(пример вывода: данные в JSON, без ошибок 401)

Этот паттерн особенно полезен для OAuth2, где токены живут, например, 15 минут.

Пример 2. Работа с несколькими сайтами в одной сессии

Если нужно последовательно обращаться к разным хостам, куки и заголовки не должны смешиваться. Создаём отдельные сессии для каждого домена.

Пример
import requests

# Сессия для site1
s1 = requests.Session()
s1.headers.update({'Host': 'site1.com'})
s1.get('https://site1.com/login')

# Сессия для site2
s2 = requests.Session()
s2.cookies.set('auth_cookie', 'xxxx', domain='site2.com')
s2.get('https://site2.com/profile')

# Запросы не пересекаются
print(s1.cookies)  # только куки site1
print(s2.cookies)  # только куки site2

Можно также использовать requests.Session() как менеджер контекста для временных сессий.

Пример 3. Настройка таймаутов и повторных попыток через адаптер

Сессия позволяет монтировать собственные адаптеры HTTP и HTTPS, чтобы задать таймауты, пул соединений и стратегию retry.

Пример
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

session = requests.Session()

retry_strategy = Retry(
    total=3,
    backoff_factor=1,
    status_forcelist=[500, 502, 503, 504],
)
adapter = HTTPAdapter(max_retries=retry_strategy, pool_connections=10, pool_maxsize=10)

# Монтируем адаптер ко всем URL
session.mount('https://', adapter)
session.mount('http://', adapter)

# Выполняем запрос с таймаутом в 5 секунд
try:
    response = session.get('https://httpbin.org/delay/1', timeout=5)
    print(response.status_code)
except requests.exceptions.Timeout:
    print('Таймаут истёк')
except requests.exceptions.ConnectionError:
    print('Ошибка соединения')
except requests.exceptions.RetryError:
    print('Исчерпаны попытки')
200 (или сообщение об ошибке, если сервер недоступен)

Параметр timeout можно задать и в общих настройках сессии через session.request, но адаптер даёт более тонкий контроль.

Пример 4. Использование прокси для всех запросов сессии

Сессия упрощает работу с прокси: достаточно задать словарь один раз.

Пример
import requests

session = requests.Session()
session.proxies = {
    'http': 'http://user:pass@proxy.example.com:8080',
    'https': 'https://user:pass@proxy.example.com:8080',
}
# Все запросы пойдут через прокси
response = session.get('https://httpbin.org/ip')
print(response.json()['origin'])  # IP прокси

Если нужно изменить прокси для одного запроса, можно передать параметр proxies в метод.

Пример 5. Потоковое скачивание файла с сохранением сессии

Скачивание большого файла с авторизацией - сессия поддерживает потоковую загрузку через stream=True.

Пример
import requests

session = requests.Session()
session.auth = ('user', 'pass')

url = 'https://httpbin.org/stream-bytes/1024'
response = session.get(url, stream=True)
response.raise_for_status()

with open('downloaded.bin', 'wb') as f:
    for chunk in response.iter_content(chunk_size=128):
        if chunk:
            f.write(chunk)
print('Файл загружен')

Важно: при потоковой загрузке соединение держится открытым, поэтому следует явно закрывать ответ (или использовать контекстный менеджер).

Пример 6. Проверка SSL-сертификата и настройка verify

Иногда требуется отключить проверку SSL или указать свой CA-файл. Это можно сделать как для всей сессии, так и для отдельного запроса.

Пример
import requests

session = requests.Session()
# Отключить проверку (не рекомендуется для продакшена)
session.verify = False
# или указать путь к сертификату
# session.verify = '/path/to/custom_ca.pem'

# Подавление предупреждения об SSL
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

response = session.get('https://self-signed.badssl.com/')
print(response.status_code)  # 200, но с предупреждением

Более правильный подход - использовать certifi или явно передавать verify в запрос:

Пример
response = session.get('https://example.com', verify=True)

Пример 7. Использование сессии с httpbin.org для отладки

Сервис httpbin.org позволяет тестировать куки, заголовки и аутентификацию. Продемонстрируем полный цикл.

Пример
import requests

session = requests.Session()
session.headers.update({'User-Agent': 'MyApp'})

# 1. Установить куки через сервер
session.get('https://httpbin.org/cookies/set?test=123')
# 2. Проверить, что куки сохранились
resp = session.get('https://httpbin.org/cookies')
print('Куки после установки:', resp.json()['cookies'])  # {'test': '123'}

# 3. Отправить запрос с базовой аутентификацией
session.auth = ('user', 'pass')
resp = session.get('https://httpbin.org/basic-auth/user/pass')
print('Аутентификация:', resp.status_code)  # 200

# 4. Проверить итоговый заголовок User-Agent
resp = session.get('https://httpbin.org/headers')
print('User-Agent:', resp.json()['headers']['User-Agent'])
Куки после установки: {'test': '123'}
Аутентификация: 200
User-Agent: MyApp

Такой подход помогает отлаживать взаимодействие с реальными API.

Пример 8. Обработка больших ответов и сжатие

Сессия поддерживает сжатие gzip/deflate автоматически. Можно явно указать заголовки Accept-Encoding.

Пример
import requests

session = requests.Session()
# Явно запросить gzip
session.headers['Accept-Encoding'] = 'gzip'

response = session.get('https://httpbin.org/gzip')
print('Содержимое:', response.text[:100])  # декомпрессия выполнена автоматически
print('Заголовки:', dict(response.headers))  # Content-Encoding: gzip

Размер ответа может быть большим - используйте iter_content для потоковой обработки.

Все примеры демонстрируют, как объект Session упрощает управление состоянием HTTP-клиента. Комбинируя описанные техники, можно строить устойчивые и производительные сетевые приложения.

Использование Session в библиотеке requests (куки, заголовки) в Python - comments

En
Request session python (python)