Основы Requests

Содержание:

  1. Знакомство с библиотекой
  2. Request объект
  3. Response объект
  4. Cессии в requests
  5. Механизмы аутентификации

Теория:

Знакомство с библиотекой

Note

Requests - это библиотека, которая помогает нам отправлять запросы к различным веб-сервисам и сайтам из нашего Python кода. Представьте, что вы открываете браузер и заходите на какой-то сайт - происходит HTTP запрос. Requests позволяет делать то же самое, но из кода

Что такое API-тестирование и почему его автоматизируют в первую очередь — в 01.01 - Введение в автоматизированное тестирование. Запускать API-тесты будем через 04.02 - Основы Pytest.

Установка библиотеки

Note

Прежде чем начать работу, нам нужно установить библиотеку. Это делается очень просто через pip (пакетный менеджер Python):

pip install requests

Note

После установки мы можем использовать библиотеку в нашем коде:

import requests  # Импортируем библиотеку

Первый запрос

Note

Работать будем с https://restful-booker.herokuapp.com/apidoc/index.html#api-Booking-GetBookings

Давайте сделаем наш первый запрос. Мы попробуем получить информацию о бронированиях с тестового API:

# Делаем GET запрос к API
response = requests.get('https://restful-booker.herokuapp.com/booking')
 
# Смотрим, что нам пришло
print(f"Статус ответа: {response.status_code}")
print(f"Тело ответа: {response.text}")

Разберём, что здесь происходит:

  1. requests.get() - это функция, которая отправляет GET запрос по указанному адресу
  2. Результат запроса сохраняется в переменную response по сути это объект, у которого есть различные атрибуты - ниже пару из них
  3. У response есть разные свойства, которые мы можем использовать:
    • status_code - код ответа (например, 200 означает успех)
    • text - текст ответа

Работа с JSON данными

Note

Часто API возвращают данные в формате JSON. Requests умеет с ними работать:

import requests
 
response = requests.get('https://restful-booker.herokuapp.com/booking')
 
data = response.json()
 
# Тело ответа в словаре
print(f"Тело ответа: {data}")
print(f"можно обратиться по ключу, например к первому элементу: {data[0]}")
 

Note

Метод .json() для объекта response преобразует ответ от сервера в формате json в словарь пайтон. Таким образом в переменной data будет словарь - json ответ от сервера.


Tip

Попробуем отправить другой запрос: Booking - GetBooking

https://restful-booker.herokuapp.com/booking/:id

Надо передать какой-то id - сделаем это через переменную!

import requests
 
booking_id = 1
response = requests.get(f'https://restful-booker.herokuapp.com/booking/{booking_id}')
 
data = response.json()
 
# Тело ответа в словаре
print(f"Тело ответа: {data}")

Tip

Да, вот так просто можно пихать переменные в урл запроса, а значит и манипулировать ими как нам нужно. Данные которые мы получили в ответе нам понадобятся дальше. Поэтому приведу их здесь (на момент написания. Это открытое Апи и в момент когда вы пробуете делать тоже самое - ответ может измениться)

Тело ответа:

{'firstname': 'Sally', 'lastname': 'Brown', 'totalprice': 990, 'depositpaid': False, 'bookingdates': {'checkin': '2023-02-13', 'checkout': '2023-04-22'}, 'additionalneeds': 'Breakfast'}

Параметры запроса

Note

Часто нам нужно передать дополнительные параметры в URL. Например, для поиска или фильтрации:

import requests
"""
response = requests.get(f'https://restful-booker.herokuapp.com/booking?firstname=Sally')
"""
response = requests.get(f'https://restful-booker.herokuapp.com/booking',
                        params={'firstname': 'Sally'})
 
data = response.json()
 
# Тело ответа в словаре
print(f"Тело ответа: {data}")

Можно это сделать это 2-мя путями:

  1. ?firstname=Sally просто написать в url - не очень красиво
  2. передать в виде аргумента параметр params который будет содержать словарь параметров. Requests автоматически составит правильный URL

Заголовки запроса

Note

Иногда нам нужно передать дополнительную информацию серверу через заголовки:

import requests
 
# Создаём словарь с заголовками
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Accept': 'application/json'
}
 
# Передаём заголовки в запрос
response = requests.get(
    'https://restful-booker.herokuapp.com/booking',
    headers=headers
)
 

Tip

Здесь в requests.get в качестве аргумента headers мы передали переменную содержащую в себе словарь с заголовками


Обработка ошибок

Note

Очень важно правильно обрабатывать возможные ошибки при работе с запросами:

try:
# Делаем запрос
    response = requests.get('https://restful-booker.herokuapp.com/booking')
 
# Проверяем статус ответа
    response.raise_for_status()  # Вызовет исключение, если статус не 2XX
 
# Если всё хорошо, работаем с данными
    data = response.json()
    print(f"Получены данные: {data}")
 
except requests.exceptions.ConnectionError:
    print("Не удалось подключиться к серверу")
# Тут можно, например, повторить запрос позже
 
except requests.exceptions.Timeout:
    print("Сервер не отвечает слишком долго")
# Тут можно увеличить время ожидания и повторить
 
except requests.exceptions.HTTPError as http_err:
    print(f"Произошла HTTP ошибка: {http_err}")
# Например, можно проверить статус код и принять решение что делать
 
except requests.exceptions.RequestException as e:
    print(f"Произошла ошибка при выполнении запроса: {e}")
# Это общий класс ошибок requests
 

POST запросы

Note

Когда нам нужно отправить данные на сервер, мы используем POST запрос:

import requests
 
# делаем словарь для отправки
data = {
    "firstname": "Jim",
    "lastname": "Brown",
    "totalprice": 111,
    "depositpaid": True,
    "bookingdates": {
        "checkin": "2025-01-04",
        "checkout": "2025-01-15"
    },
    "additionalneeds": "Breakfast"
}
 
# отправляем наш запрос
response = requests.post(
    'https://restful-booker.herokuapp.com/booking', json=data)
 
print(response.json())
 

Tip

При передаче в параметр json= словарь пайтон из переменной data преобразуется в json и запрос отправляется на сервер.

Так же обрати внимание, что у requests мы используем метод .post, а не .get
В этом плане все удобно и очевидно


🧪Практические задания

Example

Задание 1

Напишите тест, который создает бронирование (запрос который использовался в теме про POST запросы)

  1. URL по которому обращаемся положить в переменную URL чтобы каждый раз не писать/копировать/вставлять его
  2. Поскольку это тест, то нужно соответственно сделать asserts для проверки Пусть это будет “ответ от сервера == 200”

Задание 2

Сделать апгрейд теста После создания бронирования, из тела ответа от сервера нужно получить id бронирования

В тесте выполнить запрос get, где в пути будет указан id бронирования. Ассертами убедиться, что ответ успешен, а имя совпадает с тем, что было отправлено при пост запросе.


Request объект

Вводная информация

Tip

Каждый раз, когда вызывается requests.get() или аналогичные методы, выполняются два основных действия.

  1. Создается объект Request, который отправляется на сервер для запроса или получения ресурса.
  2. Генерируется объект Response, как только Requests получает ответ от сервера. Этот объект содержит всю информацию, возвращенную сервером, а также включает объект Request, который вы изначально создали.

Note

Пример простого запроса для получения важной информации с серверов Wikipedia:

r = requests.get('https://en.wikipedia.org/wiki/Monty_Python')

Note

Если нужно получить заголовки, которые сервер отправил в ответ, можно сделать следующее:

r.headers

Info

Пример вывода:

{
    'content-length': '56170',
    'x-content-type-options': 'nosniff',
    'x-cache': 'HIT from cp1006.eqiad.wmnet, MISS from cp1010.eqiad.wmnet',
    'content-encoding': 'gzip',
    'age': '3080',
    'content-language': 'en',
    'vary': 'Accept-Encoding,Cookie',
    'server': 'Apache',
    'last-modified': 'Wed, 13 Jun 2012 01:33:50 GMT',
    'connection': 'close',
    'cache-control': 'private, s-maxage=0, max-age=0, must-revalidate',
    'date': 'Thu, 14 Jun 2012 12:59:39 GMT',
    'content-type': 'text/html; charset=UTF-8',
    'x-cache-lookup': 'HIT from cp1006.eqiad.wmnet:3128, MISS from cp1010.eqiad.wmnet:80'
}

Tip

Чтобы получить заголовки, которые вы отправили серверу, можно обратиться к объекту request и затем к его заголовкам:

r.request.headers

Info

Пример вывода:

{
    'Accept-Encoding': 'identity, deflate, compress, gzip',
    'Accept': '*/*',
    'User-Agent': 'python-requests/1.2.0'
}

Типы запросов

Библиотека requests позволяет отправлять HTTP-запросы различных типов. Основные методы:

  • requests.get(): GET-запрос (получение данных).
  • requests.post(): POST-запрос (отправка данных на сервер).
  • requests.put(): PUT-запрос (обновление данных на сервере).
  • requests.delete(): DELETE-запрос (удаление данных на сервере).
  • requests.patch(): PATCH-запрос (частичное обновление данных).
  • requests.head(): HEAD-запрос (получение только заголовков ответа).
  • requests.options(): OPTIONS-запрос (получение информации о доступных методах для ресурса).

Note

У библиотеки requests есть метод request(), который позволяет передавать HTTP-метод в виде параметра. Это более общий и гибкий способ отправки запросов, чем использование отдельных методов get(), post(), put()

Пример:

response = requests.request(method=method, url=url)

Note

  • method: HTTP-метод (строка, например, “GET”, “POST”, “PUT”, “DELETE”, “PATCH”, “HEAD”, “OPTIONS”).
  • url: URL запроса (строка).
  • Дополнительные параметры, такие же, как и у методов get(), post() и т.д. (например, params, data, json, headers, cookies, auth, timeout)
"""То есть абсолютно идентичны следующие варианты написания:"""
 
base_url = 'https://restful-booker.herokuapp.com/booking'
 
response = requests.get(url=base_url)
response = requests.request(method="GET", url=base_url)

Основные параметры для request

ПараметрТип данныхОписаниеПример
urlstrURL запроса.https://httpbin.org/get
paramsdict или list из кортежей (key, value)Query-параметры.{“key1”: “value1”, “key2”: “value2”} или [(“key1”, “value1”), (“key2”, “value2”)]
datadict, list из кортежей, str, bytesДанные в теле запроса (для POST, PUT, PATCH).{“key1”: “value1”, “key2”: “value2”}, “string data”, b”byte data”
jsondict, list, int, str и др.Данные в формате JSON (для POST, PUT, PATCH). Requests автоматически сериализует данные в JSON.{“key1”: “value1”}
headersdictЗаголовки запроса.{“Content-Type”: “application/json”, “User-Agent”: “MyBot”}
cookiesdict или RequestsCookieJarКуки.{“cookie_name”: “cookie_value”}
filesdictФайлы для отправки (multipart/form-data). Значения словаря должны быть файлоподобными объектами или кортежами (filename, fileobject).{“file”: open(“my_file.txt”, “rb”)} или {“file”: (“my_file.txt”, open(“my_file.txt”, “rb”), “text/plain”)}
authtuple ((username, password)) или объект AuthBaseАутентификация.(“myuser”, “mypassword”)

payload или body

Note

Отправка пейлоада в запросе

Пейлоад — это данные, которые вы отправляете на сервер вместе с запросом (обычно в POST, PUT, PATCH). requests предоставляет несколько способов отправки пейлоада, в зависимости от формата данных

Параметр data

Tip

Параметр data используется для отправки данных в формате x-www-form-urlencoded (как обычная HTML-форма) или в виде произвольной строки (например, XML, plain text).

  • x-www-form-urlencoded (словарь или список кортежей):

    import requests
     
    url = "https://httpbin.org/post"
    data = {"key1": "value1", "key2": "value2"}
    response = requests.post(url, data=data) # requests сам кодирует словарь в x-www-form-urlencoded
    print(response.request.body) # Показывает закодированное тело запроса
    print(response.text)

Tip

В этом случае requests автоматически кодирует словарь data в строку вида key1=value1&key2=value2.

Note

Можно также использовать список кортежей:

```python
data = [("key1", "value1"), ("key2", "value2")]
response = requests.post(url, data=data)
```
  • Произвольная строка (str или bytes):

    import requests
     
    url = "https://httpbin.org/post"
    data = "<xml><data>some data</data></xml>"  # Отправка XML
    response = requests.post(url, data=data)
    print(response.request.body)
    print(response.text)
     
    data_bytes = b"raw bytes data"
    response_bytes = requests.post(url, data=data_bytes)
    print(response_bytes.request.body)
    print(response_bytes.text)

Tip

Здесь данные отправляются как есть, без кодирования. Важно правильно установить заголовок Content-Type, чтобы сервер знал, как интерпретировать данные (например, Content-Type: application/xml или Content-Type: text/plain).


Параметр json

Tip

Параметр json используется для отправки данных в формате JSON. requests автоматически сериализует переданный объект Python (обычно словарь или список) в JSON

import requests
 
url = "https://httpbin.org/post"
data = {"key1": "value1", "key2": "value2"}
response = requests.post(url, json=data) # requests автоматически сериализует в JSON
print(response.request.body) # Показывает JSON в теле запроса
print(response.json()) # Декодированный JSON ответ сервера

Tip

В этом случае requests преобразует словарь data в JSON-строку {"key1": "value1", "key2": "value2"} и устанавливает заголовок Content-Type: application/json


🧪Практическое задание

Example

Задание 1:

  1. Отправить POST запрос, урл и дата ниже, передав payload в параметр json: (ответ от сервера можно напечатать как print(response.text) ) (посмотреть тело отправки запроса print(response.request.body) )

    url = '[https://restful-booker.herokuapp.com/booking](https://restful-booker.herokuapp.com/booking)'
    payload = {
        "firstname": "Jim",
        "lastname": "Brown",
        "totalprice": 111,
        "depositpaid": True,
        "bookingdates": {
            "checkin": "2025-01-04",
            "checkout": "2025-01-15"
        },
        "additionalneeds": "Breakfast"
    }
  2. Теперь изменить параметр отправки с json на data Повторить запрос - ознакомиться с ответом и тем, как был отправлен body в запросе


Response объект

Структура объекта Response

Объект Response имеет следующие основные атрибуты и методы:

  • status_code: Код статуса HTTP-ответа (например, 200 OK, 404 Not Found, 500 Internal Server Error)
  • headers: Словарь, содержащий заголовки ответа
  • content: Содержимое ответа в виде байтовой строки (bytes)
  • text: Содержимое ответа в виде строки Unicode (получается путем декодирования content с использованием определенной кодировки)
  • json(): Метод для декодирования JSON-ответа в объект Python (словарь или список)
  • raw: Необработанный ответ от сервера (объект urllib3.response.HTTPResponse)
  • url: URL-адрес, на который был отправлен запрос
  • history: Список объектов Response, представляющих историю перенаправлений (если они были)
  • cookies: Объект RequestsCookieJar, содержащий куки, установленные сервером
  • encoding: Кодировка, используемая для декодирования content в text
  • elapsed: Объект datetime.timedelta, представляющий время, затраченное на выполнение запроса
  • request: Объект PreparedRequest, представляющий отправленный запрос
  • reason: Текстовое описание статуса (например, “OK”, “Not Found”)

Info

На примере:

import requests
 
url = "https://httpbin.org/get"
response = requests.get(url)
 
print(f"Status Code: {response.status_code}")
print(f"Headers: {response.headers}")
print(f"Content (bytes): {response.content}")
print(f"Text (string): {response.text}")
print(f"URL: {response.url}")
print(f"History: {response.history}")
print(f"Cookies: {response.cookies}")
print(f"Encoding: {response.encoding}")
print(f"Elapsed Time: {response.elapsed}")
print(f"Request: {response.request}")
print(f"Reason: {response.reason}")

Работа с контентом

Tip

response.content содержит необработанные байты ответа. Это для работы с бинарными данными, такими как изображения или файлы

response.text — получение текстового содержимого ответа

Tip

response.text декодирует содержимое ответа в строку Unicode. requests автоматически декодирует байты, полученные от сервера, в строку Unicode, что позволяет легко работать с текстовыми данными

import requests
 
url = "https://httpbin.org/get"  # Простой эндпоинт, возвращающий информацию о запросе
response = requests.get(url)
 
if response.status_code == 200: # Проверяем успешность запроса
    print(f"Текст ответа:\n{response.text}") # Выводим текст ответа
else:
    print(f"Ошибка запроса: {response.status_code}")
    print(f"Текст ошибки:\n{response.text}") # Выводим текст ошибки, если она 

Разъяснения:

  1. response.text: Этот атрибут содержит декодированное текстовое содержимое ответа. requests пытается автоматически определить кодировку, используя заголовки Content-Type ответа
  2. Unicode: Текст, полученный через response.text, представлен в кодировке Unicode, что позволяет корректно отображать символы различных языков
  3. Пример с ошибкой: В примере добавлен вывод текста ошибки, если запрос не успешен. Это для отладки
  • Пустой ответ: Если сервер возвращает пустой ответ (например, с кодом 204 No Content или в результате ошибки), response.text будет содержать пустую строку ("")
  • Ошибка 500 (Internal Server Error): Если сервер возвращает ошибку 500, содержимое ответа может быть различным. Часто это HTML-страница с сообщением об ошибке, но может быть и пустой ответ, или даже JSON с описанием ошибки (хотя это менее распространено для 500).
    • Если ответ — HTML: response.text будет содержать HTML-код страницы ошибки
    • Если ответ пустой: response.text будет пустой строкой

Кодировка (Опционально)

Tip

Кодировки и response.encoding

Кодировка определяет, как символы представляются в виде байтов. Разные кодировки используют разные наборы символов и правила их преобразования в байты

requests пытается автоматически определить кодировку ответа. Эта определенная кодировка хранится в атрибуте response.encoding

В большинстве случаев автоматическое определение кодировки работает корректно, и вам не нужно ничего менять. Однако в редких случаях, когда автоматическое определение неверно, вы можете явно указать кодировку, изменив значение response.encoding перед обращением к response.text

Пример (демонстрационный):

import requests
 
url = "https://httpbin.org/encoding/utf8"  # Пример, где httpbin сообщает кодировку utf-8
response = requests.get(url)
 
print(f"Кодировка до изменения: {response.encoding}")
print(f"Текст до изменения:\n{response.text}")
 
response.encoding = 'utf-8'  # Явно устанавливаем utf-8 (в данном случае это избыточно, так как httpbin уже указал эту кодировку)
print(f"Кодировка после изменения: {response.encoding}")
print(f"Текст после изменения:\n{response.text}")
 
url_latin = "https://httpbin.org/encoding/utf8" # Пример, где мы *ошибочно* предполагаем latin1
response_latin = requests.get(url_latin)
response_latin.encoding = 'latin1' # *Неправильная* кодировка, приведет к искажению текста
print(f"Текст с (неправильной) кодировкой latin1:\n{response_latin.text}")
 
# Правильный подход:
response_latin_correct = requests.get(url_latin)
response_latin_correct.encoding = response_latin_correct.apparent_encoding # используем apparent_encoding
print(f"Текст с (автоматически определенной) кодировкой:\n{response_latin_correct.text}")

Разъяснения:

  1. response.encoding: Содержит текущую кодировку
  2. Изменение response.encoding: Изменение этого атрибута влияет на то, как response.text декодирует байты
  3. apparent_encoding: Если сервер не указал кодировку явно, можно воспользоваться методом response.apparent_encoding, который пытается угадать кодировку по содержимому ответа
  4. Важно: Изменять кодировку нужно только в случае необходимости, когда автоматическое определение неверно. В большинстве случаев это не требуется. Использование неправильной кодировки приведет к искажению текста (кракозябры)

Работа с JSON ответами

Note

Если ответ имеет формат JSON, используйте метод response.json() для его декодирования в объект Python (словарь или список):

import requests
 
url = "https://httpbin.org/json"
response = requests.get(url)
data = response.json()
print(type(data))
print(data)
print(data['slideshow']['author'])c

Некоторые моменты:

  1. Исключение requests.exceptions.JSONDecodeError. Это исключение возникает, если содержимое ответа не является валидным JSON. Например, если сервер вернул HTML-страницу с ошибкой или просто пустой ответ

  2. Мы можем обрабатывать исключение requests.exceptions.JSONDecodeError, например:

    try:
        data = response.json()  # Пытаемся декодировать JSON
        print(type(data))  # Выведет <class 'dict'>
        print(data)  # Выведет содержимое JSON
        print(data['slideshow']['author']) # Доступ к вложенным элементам
    except requests.exceptions.JSONDecodeError as e:
        print(f"Ошибка декодирования JSON: {e}")
        print(f"Текст ответа: {response.text}") # Выводим текст ответа для отладки
    except Exception as e:
        print(f"Другая ошибка: {e}")
     

Info

Давайте попробуем на практике на это дело посмотреть:

import requests
 
urls = [
    "https://httpbin.org/json",  # Нормальный JSON
    "https://httpbin.org/status/204", # Пустой ответ
    "https://httpbin.org/status/500",  # Ошибка 500
    "https://httpbin.org/html", # HTML
]
 
for url in urls:
    print(f"URL: {url}")
    response = requests.get(url)
    print(f"Status code: {response.status_code}")
    try:
        data = response.json()
        print(f"JSON data: {data}")
    except requests.exceptions.JSONDecodeError:
        print(f"Ошибка декодирования JSON. Текст ответа: {response.text}") # Выводим первые 100 символов
    except Exception as e:
        print(f"Другая ошибка: {e}")
    print("-" * 20)

Tip

Скопируйте этот код и запустите его. В выводе надо обратить внимание, что в некоторых частях response.text ничего не выводит (пустая строка), но и исключение мы обработали

🧪Практическое задание:

Example

  1. Скопировать код выше и запустить его
  2. Написать запрос без обработки ошибок на url “https://httpbin.org/status/204” и попробовать преобразовать json. Ознакомиться с результатом работы (исключением)

Обработка XML и HTML

Note

Данный контент для общего понимания. Вряд ли вы с ним столкнетесь, а если и столкнетесь - то документации или чат в помощь. Все же мы будем опираться в курсе на text или json

Note

Для обработки XML и HTML рекомендуется использовать сторонние библиотеки, такие как lxml или Beautiful Soup:

import requests
from bs4 import BeautifulSoup # Для HTML
import lxml.etree as ET # Для XML
 
url_html = "https://httpbin.org/html"
response_html = requests.get(url_html)
soup = BeautifulSoup(response_html.text, 'html.parser')
print(soup.title)
 
url_xml = "https://www.w3schools.com/xml/note.xml"
response_xml = requests.get(url_xml)
tree = ET.fromstring(response_xml.content)
print(tree.find('to').text)

Сессии в requests

Tip

Сессия в requests представлена объектом requests.Session. Она позволяет сохранять состояние между несколькими запросами к одному и тому же серверу. Это особенно полезно для:

  • Сохранения cookies: Автоматическое управление cookies между запросами
  • Повторного использования соединений: Ускорение последующих запросов за счет повторного использования TCP-соединений (пулинг соединений)
  • Установки параметров по умолчанию: Установка заголовков, параметров аутентификации и других настроек, которые будут применяться ко всем запросам в рамках сессии

Механика Session объекта

Tip

Когда вы создаете объект Session, requests создает контейнер для хранения состояния. При каждом запросе, сделанном с помощью этого объекта, requests автоматически добавляет сохраненные cookies, заголовки и другие параметры к запросу. Ответ сервера также обрабатывается, и новые cookies сохраняются в сессии


Постоянные параметры

Tip

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

import requests
 
s = requests.Session()
s.headers.update({'User-Agent': 'My Custom Bot'}) # Постоянный заголовок
s.auth = ('user', 'password') # Постоянная аутентификация
 
response = s.get('https://httpbin.org/headers')
print(response.json())
 
response2 = s.post('https://httpbin.org/post', json={"data": "test"})
print(response2.json())

Разбор по шагам:

Note

1️⃣

import requests: Импортируем библиотеку requests

Note

2️⃣

s = requests.Session(): Создаем экземпляр объекта Session и присваиваем его переменной s. Это создает контейнер для хранения состояния сессии, включая постоянные параметры

Note

3️⃣

s.headers.update({'User-Agent': 'My Custom Bot'}): Здесь мы устанавливаем постоянный заголовок User-Agent. Метод update() объекта s.headers (который является словарем) добавляет или обновляет указанный заголовок. Теперь каждый запрос, сделанный с использованием этой сессии s, будет автоматически включать заголовок User-Agent: My Custom Bot

Note

4️⃣

s.auth = ('user', 'password'): Устанавливаем постоянную аутентификацию Basic Auth. Кортеж ('user', 'password') содержит имя пользователя и пароль. Теперь каждый запрос с этой сессией будет автоматически отправлять заголовок Authorization с закодированными учетными данными

Note

5️⃣

response = s.get('https://httpbin.org/headers'): Выполняем GET-запрос к URL https://httpbin.org/headers. Сервис httpbin.org/headers возвращает заголовки, которые были отправлены в запросе

  • Важно: Перед отправкой запроса сессия s автоматически добавляет к нему постоянные параметры: заголовок User-Agent и заголовок Authorization (для аутентификации)
  • print(response.json()) выводит JSON-ответ, который содержит все заголовки, полученные сервером. Вы увидите, что User-Agent установлен в My Custom Bot, а также будет присутствовать заголовок Authorization

Note

6️⃣

response2 = s.post('https://httpbin.org/post', json={"data": "test"}): Выполняем POST-запрос к URL https://httpbin.org/post с JSON-данными {"data": "test"}

  • Важно: Как и в предыдущем случае, сессия s автоматически добавляет к этому запросу те же самые постоянные параметры: заголовок User-Agent и заголовок Authorization. Кроме того, поскольку мы использовали параметр json=..., requests автоматически устанавливает заголовок Content-Type: application/json
  • print(response2.json()) выводит JSON-ответ сервера, который содержит информацию о запросе, включая отправленные данные и заголовки

Note

Ключевые моменты:

  • Параметры, установленные непосредственно в методах запроса (get(), post(), и т.д.), применяются только к этому конкретному запросу
  • Параметры, установленные на уровне сессии (с помощью s.headers, s.auth, s.proxies, s.cookies и т.д.), применяются ко всем запросам, сделанным с использованием этой же сессии
  • Постоянные параметры не перезаписывают параметры, указанные на уровне метода. Если вы укажете заголовок User-Agent как в сессии, так и в методе get(), значение, указанное в методе, будет иметь приоритет для этого конкретного запроса

Сохранение состояния между запросами

Note

Главное преимущество сессии — сохранение состояния. Рассмотрим пример с cookies:

import requests
 
s = requests.Session()
 
# Первый запрос (сервер устанавливает cookie)
response1 = s.get('https://httpbin.org/cookies/set/sessioncookie/123456789')
print(response1.text)
 
# Второй запрос (cookie автоматически отправляется)
response2 = s.get('https://httpbin.org/cookies')
print(response2.json()) # Видим, что cookie был отправлен

Параметры на уровне метода не сохраняются

Note

Параметры, указанные на уровне метода, не сохраняются между запросами, даже если используется сессия:

s = requests.Session()
 
# Указываем cookie для первого запроса
r = s.get('https://httpbin.org/cookies', cookies={'from-my': 'browser'})
print(r.text)
# Вывод: '{"cookies": {"from-my": "browser"}}'
 
# Второй запрос не использует cookie
r = s.get('https://httpbin.org/cookies')
print(r.text)
# Вывод: '{"cookies": {}}'

Question

Почему? Разбираем:

  • Первый пример - почему кука сохранилась

    1. s = requests.Session(): Создается объект сессии s. Этот объект будет хранить состояние между запросами, включая куки
    2. response1 = s.get('https://httpbin.org/cookies/set/sessioncookie/123456789'): Выполняется GET-запрос к URL https://httpbin.org/cookies/set/sessioncookie/123456789. Этот URL на сервисе httpbin.org специально предназначен для установки куки. Сервер в ответ на этот запрос отправляет заголовок Set-Cookie, который указывает браузеру (в данном случае, requests) сохранить куку с именем sessioncookie и значением 123456789
    3. Важно: Объект сессии s автоматически обрабатывает заголовок Set-Cookie из ответа response1 и сохраняет полученную куку внутри себя
    4. response2 = s.get('https://httpbin.org/cookies'): Выполняется второй GET-запрос, используя ту же самую сессию s

    Ключевой момент: сервер устанавливает куку, а сессия requests автоматически ее сохраняет

  • Второй пример - почему кука не сохранилась в сессии

    1. s = requests.Session(): Создается объект сессии s
    2. r = s.get('https://httpbin.org/cookies', cookies={'from-my': 'browser'}): Выполняется GET-запрос, и при этом кука from-my=browser передается непосредственно в параметре cookies метода get()
    3. Важно: Кука {'from-my': 'browser'} была отправлена только для этого конкретного запроса. Объект сессии s не сохраняет эту куку автоматически, потому что она была передана на уровне метода, а не установлена сервером с помощью заголовка Set-Cookie.
    4. r = s.get('https://httpbin.org/cookies'): Выполняется второй GET-запрос, используя ту же самую сессию s
    5. Ключевой момент: Так как сессия s не сохранила куку из предыдущего запроса (она была передана только для одного запроса), во втором запросе заголовок Cookie отсутствует

🧪Практическое задание

Example

Берем код из примера - повторяем, смотрим вывод В идеале еще код и руками переписать


Конфигурация сессий

Основные настройки сессий:

  • headers: Постоянные заголовки.

  • auth: Постоянная аутентификация.

  • proxies: Постоянные прокси.

  • cookies: Куки, которые будут отправляться с каждым запросом.

  • verify: Проверка SSL-сертификата (по умолчанию True)

  • cert: Путь к файлу сертификата клиента (для клиентской аутентификации)

  • timeout: Таймаут по умолчанию для запросов

  • max_redirects: Максимальное количество перенаправлений

  • Настройка постоянных заголовков

    Как показано в примере выше, заголовки можно установить с помощью s.headers.update()

  • Управление cookie

    Куки можно получить из ответа с помощью response.cookies (это объект RequestsCookieJar) и установить в сессии с помощью s.cookies.update()


Сравниваем скорость

Info

Давайте взглянем на простую демонстрацию:

import requests
import time
 
url = "https://httpbin.org/get"
num_requests = 10
 
print("Запросы с использованием сессии:")
start_time = time.time()
 
session = requests.Session()  # Создаем сессию
try:
    for i in range(num_requests):
        response = session.get(url)
        response.raise_for_status()
except Exception as e:
    print(f"ошибка: {e}")
finally:
    session.close()  # закрываем сессию
end_time = time.time()
print(f"Время выполнения с сессией: {end_time - start_time:.4f} секунд\n")
 
# Запросы БЕЗ использования сессии:
print("Запросы БЕЗ использования сессии:")
start_time = time.time()
try:
    for i in range(num_requests):
        response = requests.get(url)
        response.raise_for_status()
except Exception as e:
    print(f"Ошибка: {e}")
end_time = time.time()
print(f"Время выполнения без сессии: {end_time - start_time:.4f} секунд")
 
  • Разбор кода по шагам:

    1. Импорт модулей:

    • import requests: Импортирует библиотеку requests для выполнения HTTP-запросов
    • import time: Импортирует модуль time для измерения времени выполнения

    2. Определение параметров:

    • url = "https://httpbin.org/get": Определяет URL-адрес, к которому будут отправляться запросы. https://httpbin.org/get — это эндпоинт, который возвращает информацию о запросе (включая заголовки, IP-адрес и т. д.)
    • num_requests = 10: Определяет количество запросов, которые будут выполнены в каждом тесте (с сессией и без)

    3. Запросы с использованием сессии:

    • print("Запросы с использованием сессии:"): Выводит сообщение о начале теста с сессией
    • start_time = time.time(): Записывает текущее время перед началом выполнения запросов
    • session = requests.Session(): Создает объект сессии. Это ключевой момент. Сессия будет использоваться для всех последующих запросов
    • try...except...finally: Блок обработки исключений
      • try: Внутри этого блока выполняются запросы.
        • for i in range(num_requests):: Цикл, выполняющий num_requests запросов
        • response = session.get(url): Выполняет GET-запрос к указанному URL, используя созданную сессию
        • response.raise_for_status(): Проверяет код статуса HTTP-ответа. Если код находится в диапазоне 4xx или 5xx (ошибки), выбрасывается исключение requests.exceptions.HTTPError. Это позволяет обрабатывать ошибки запросов
      • except Exception as e: Обрабатывает любые исключения, которые могут возникнуть во время выполнения запросов (например, ошибки соединения, таймауты и т. д.). Выводит сообщение об ошибке
      • finally: Блок, который выполняется всегда, независимо от того, произошло исключение или нет
        • session.close(): Закрывает сессию. Это очень важно для освобождения ресурсов
    • end_time = time.time(): Записывает текущее время после завершения выполнения запросов
    • print(f"Время выполнения с сессией: {end_time - start_time:.4f} секунд\n"): Выводит время, затраченное на выполнение всех запросов с использованием сессии

    4. Запросы БЕЗ использования сессии:

    • print("Запросы БЕЗ использования сессии:"): Выводит сообщение о начале теста без сессии
    • start_time = time.time(): Записывает текущее время перед началом выполнения запросов
    • try...except: Блок обработки исключений (блок finally здесь не нужен, так как сессия не создается)
      • for i in range(num_requests):: Цикл, выполняющий num_requests запросов
      • response = requests.get(url): Выполняет GET-запрос к указанному URL. Каждый раз создается новое соединение
      • response.raise_for_status(): Проверяет код статуса ответа
    • except Exception as e: Обрабатывает возможные исключения
    • end_time = time.time(): Записывает текущее время после завершения выполнения запросов
    • print(f"Время выполнения без сессии: {end_time - start_time:.4f} секунд"): Выводит время, затраченное на выполнение всех запросов без использования сессии

Example

А вот на сами результаты я предлагаю вам посмотреть самостоятельно! Скопируйте код и запустите его. Полагаю результаты произведут впечатление


Механизмы аутентификации

Базовая аутентификация (Basic Authentication)

Tip

Basic Auth — это простой метод аутентификации, где имя пользователя и пароль передаются серверу. requests предоставляет удобный способ использования Basic Auth с помощью параметра auth

Пример:

import requests
from requests.auth import HTTPBasicAuth
 
url = "https://httpbin.org/basic-auth/user/passwd"  # URL, требующий Basic Auth
 
response = requests.get(url, auth=HTTPBasicAuth('user', 'passwd'))
 
print(f"Status code: {response.status_code}")
print(response.json())

Разъяснения:

  • from requests.auth import HTTPBasicAuth: Импортируем класс HTTPBasicAuth. Этот класс отвечает за форматирование учетных данных для Basic Auth
  • auth=HTTPBasicAuth('user', 'passwd'): Параметр auth принимает экземпляр класса HTTPBasicAuth, которому передаются имя пользователя и пароль в виде строк. requests автоматически выполняет кодирование учетных данных в Base64 и добавляет заголовок Authorization в запрос
  • response.status_code: Код статуса HTTP-ответа. 200 означает успешную аутентификацию
  • response.json(): Преобразует JSON-ответ в словарь Python. В данном случае, если аутентификация успешна, httpbin возвращает JSON с информацией об аутентифицированном пользователе

Question

Что происходит “под капотом” при использовании auth?

Когда вы используете auth=HTTPBasicAuth('user', 'passwd'), requests выполняет следующие действия:

  1. Формирует строку username:password
  2. Кодирует эту строку в Base64
  3. Создает заголовок Authorization в формате Basic <encoded_credentials>, где <encoded_credentials> — это строка в Base64
  4. Отправляет запрос с этим заголовком

Note

Вам не нужно беспокоиться о ручном кодировании, HTTPBasicAuth делает это за вас.

Пример с неправильными учетными данными:

import requests
from requests.auth import HTTPBasicAuth
 
url = "https://httpbin.org/basic-auth/user/passwd"
 
response_wrong = requests.get(url, auth=HTTPBasicAuth('wrong_user', 'wrong_passwd'))
 
print(f"Status code (wrong credentials): {response_wrong.status_code}") # Статус код будет 401 (Unauthorized)
print(response_wrong.text) # Тело ответа будет содержать сообщение об ошибке

Tip

В этом случае сервер вернет код состояния 401 Unauthorized, показывая, что аутентификация не удалась.


Bearer Token

Note

Bearer token — это строка (токен), используемая для аутентификации. Обычно передается в заголовке Authorization с префиксом Bearer

import requests
 
url = "https://httpbin.org/bearer"
token = "my_secret_token"
headers = {'Authorization': f'Bearer {token}'}
 
response = requests.get(url, headers=headers)
 
print(f"Status code: {response.status_code}")
print(response.json())

Разъяснения:

  • headers = {'Authorization': f'Bearer {token}'}: Создаем словарь с заголовком Authorization, значение которого начинается с Bearer, за которым следует сам токен

API Key (ключ API)

Note

API key — это строка, идентифицирующая приложение или пользователя. API key может передаваться разными способами, но чаще всего в заголовке или параметре запроса

Все аналогично bearer

Пример (в заголовке X-API-Key):

import requests
 
url = "https://httpbin.org/headers"
api_key = "my_api_key"
headers = {'X-API-Key': api_key}
 
response = requests.get(url, headers=headers)
 
print(f"Status code: {response.status_code}")
print(response.json()) # В json будет информация о переданных заголовках, включая X-API-Key

Пример (в параметре запроса):

import requests
 
url = "https://httpbin.org/get"
api_key = "my_api_key"
params = {'api_key': api_key}
 
response = requests.get(url, params=params)
 
print(f"Status code: {response.status_code}")
print(response.json()) # В json будет информация о переданных параметрах, включая api_key

Refresh Token

Tip

Refresh Token - это специальный токен, который используется для обновления access токена (токена доступа). Access токен имеет ограниченный срок действия, и когда он истекает, пользователь должен снова авторизоваться. Чтобы избежать этого, используется refresh токен. Он имеет более длительный срок действия и позволяет получить новый access токен без повторной авторизации пользователя.

Процесс обновления access токена выглядит так:

  1. Пользователь получает access токен и refresh токен при авторизации
  2. Когда срок действия access токена истекает, приложение отправляет запрос на сервер с refresh токеном
  3. Сервер проверяет refresh токен и, если он действителен, выдает новый access токен и, возможно, новый refresh токен - тут уже зависит от реализации
  4. Приложение сохраняет новый access токен и refresh токен (если он был обновлен) и использует новый access токен для дальнейших запросов

Пример использования

import requests
 
REFRESH_TOKEN_URL = "https://example.com/api/token/refresh"
 
refresh_token = "YOUR_REFRESH_TOKEN"
 
def refresh_access_token(refresh_token):
    """Обновляет access токен с помощью refresh токена."""
    data = {"refresh_token": refresh_token}
    response = requests.post(REFRESH_TOKEN_URL, json=data)
 
    if response.status_code == 200:
        new_access_token = response.json().get("access_token")
        new_refresh_token = response.json().get("refresh_token")
        return new_access_token, new_refresh_token
    else:
        print(f"Ошибка обновления токена: {response.status_code}")
        return None, None
 
# Получаем оба токена
new_access_token, new_refresh_token = refresh_access_token(refresh_token)
 
if new_access_token:
    print(f"Новый access токен: {new_access_token}")
    if new_refresh_token:
        print(f"Новый refresh токен: {new_refresh_token}")
 
    # Дальше суем в заголовок/куки - в зависимости от реализации
    headers = {"Authorization": f"Bearer {new_access_token}"}
    response = requests.get("https://example.com/api/protected", headers=headers)
    # ...

OAuth2

Tip

OAuth2 (Open Authorization) - это протокол авторизации, который предоставляет сторонним приложениям ограниченный доступ к ресурсам пользователя на другом сервисе, не требуя передачи логина и пароля

Сравним чем отличается от просто авторизации по логин-паролю:

Note

1️⃣

  • OAuth2: Вместо передачи логина и пароля, приложение получает специальный токен доступа (access token), который используется для доступа к ресурсам пользователя. Даже в случае компрометации токена, пароль пользователя остается защищенным
  • Простая авторизация: Приложение получает доступ к логину и паролю пользователя, что создает риск их компрометации

Note

2️⃣

  • OAuth2: предоставляет приложению только те права доступа, которые необходимы для его работы. Например, приложение может получить доступ только к чтению профиля пользователя, но не к его электронной почте
  • Простая авторизация: Обычно предоставляет приложению полный доступ к ресурсам пользователя

Note

3️⃣

  • OAuth2: Пользователь может в любой момент отозвать доступ приложения к своим ресурсам
  • Простая авторизация: Для отзыва доступа часто требуется изменение пароля

Note

4️⃣

  • OAuth2: Позволяет пользователю делегировать доступ к своим ресурсам сторонним приложениям, сохраняя контроль над своими данными
  • Простая авторизация: Не предоставляет таких возможностей

Схема работы OAuth2

Note

1️⃣

Приложение запрашивает у пользователя разрешение на доступ к его ресурсам на сервере авторизации (например, Google, Facebook и т.д.)

Note

2️⃣

Пользователь предоставляет разрешение, и сервер авторизации выдает приложению токен доступа.

Note

3️⃣

Приложение использует токен для доступа к ресурсам пользователя на сервере ресурсов (это может быть тот же сервер авторизации или другой сервер)

Info

Выглядит это точно так же как и обычная авторизация, но все же:

import requests
 
# Готовим данные
auth_url = 'https://api.example.com/oauth2/token'
client_id = 'your_client_id'
client_secret = 'your_client_secret'
 
data = {
    'grant_type': 'client_credentials',
    'client_id': client_id,
    'client_secret': client_secret
}
 
# Получаем токен
token_response = requests.post(auth_url, data=data)
access_token = token_response.json().get('access_token')
 
# используем
headers = {
    'Authorization': f'Bearer {access_token}'
}
 
response = requests.get('https://api.example.com/secure-data', headers=headers)
 
if response.status_code == 200:
    print("Авторизация через OAuth2 успешна!")
    print(response.json())

⬅️ Назад: 04.02 - Основы Pytest | Далее: 04.04 - Дебаггер в PyCharm ➡️ Модуль: 04 - MOC