Библиотека Requests в Python

Оригинал: https://realpython.com/python-requests/

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

Он абстрагирует сложности создания запросов за красивым и простым API, чтобы вы могли сосредоточиться на взаимодействии с сервисами, и использовании данных в своем приложении.

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

Вы узнаете как эффективно использовать запросы и как предотвратить замедление запросов к внешним службам вашего приложения.

Из данного руководства вы узнаете как:

  • делать запросы, используя наиболее распространенные методы HTTP;
  • настраивать заголовки HTTP и данные, используя строки запросов и тело сообщений;
  • проверять данные из ваших запросов и ответов;
  • делать запросы с аутентификацией;
  • настраивать запросы для предотвращения резервного копирования и замедления работы приложения.

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

Для того, чтобы ознакомится с базовыми понятиями работы HTTP-запросов, можно прочитать статью w3schools.

Начало работы с библиотекой requests

Начнем с установки библиотеки запросов. Для этого в консоли выполните команду:

pip install requests

Если вы используете pipenv для управления пакетами python, то выполните команду:

pipenv install requests

После установки библиотеки, вы можете импортировать ее в приложение:

import requests

Теперь, когда всё настроено, самое время начать работать с библиотекой. Первой целью будет научиться как делать GET-запросы.

GET-запросы

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

Одним из наиболее распространенных методов HTTP является GET. Метод GET указывает, что вы пытаетесь извлечь из указанного ресурса. Чтобы сделать GET запрос, вызовите requests.get().
Чтобы проверить это, вы можете сделать запрос к API Гитхаба, вызвав get() со следующим URL:

requests.get('https://api.github.com')

Поздравляем! Вы сделали первый запрос. Давайте теперь погрузимся немного глубже в ответ на этот запрос.

Объект Response

Response - это объект для проверки результатов запроса.

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

response = requests.get('https://api.github.com')

В этом примере вы захватили значение, возвращаемое значение get(), которое является экземпляром Response, и сохранили его в переменной response. Название переменной может быть любым.

Код ответа HTTP

Первый кусок данных, который можно получить из ответа - код состояния (он же код ответа HTTP). Код ответа информирует вас о состоянии запроса.

Например, статус 200 OK означает, что ваш запрос был успешно выполнен, а статус 404 NOT FOUND означает, что ресурс не найден. Есть множество других ответов сервера, которые могут дать вам информацию о том, что произошло с вашим запросом.

Используя .status_code вы можете увидеть статус, который вернул вам в ответ сервер:

response.status_code

.status_code вернул 200 — это значит, что запрос успешно выполнен и сервер отдал вам запрашиваемые данные.

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

if response.status_code == 200:
    print('Success')
elif response.status_code == 404:
    print('Not Found')

Если сервер возвращает 200, то программа выведет Success, если код ответа 400, то программа выведет Not Found.

Requests делает еще один шаг к тому, чтобы сделать это проще. Если вы используете экземпляр Response в условном выражении, то он получит значение True, если код ответа между 200 и 400, и False во всех остальных случаях.

Поэтому вы можете сделать проще последний пример, переписав if:

if response:
    print('Success')
else:
    print('An error has occurred.')

Техническая деталь: Этот тест истинного значения возможен благодаря тому, что __bool__() — перегруженный метод в Response.
Это означает, что стандартное поведение Response было переопределено для учета кода состояния (ответа сервера) при опеределении значения истинности.

Помните, что этот метод не проверяет, что код состояния равен 200.
Причиной этого является то, что ответы с кодом в диапазоне от 200 до 400, такие как 204 NO CONTENT и 304 NOT MODIFIED, тоже считаются истинными, так как они дают некоторый обрабатываемый ответ.

Например, статус 204 говорит о том, что запрос был успешным, но в теле ответа нет содержимого.

Поэтому убедитесь, что вы используете этот сокращенный вид записи, только если хотите узнать был ли запрос успешен в целом. А затем обработать код состояния соответствующим образом.

Если вы не хотите проверять код ответа сервера в операторе if, то вместо этого вы можете вызвать исключение, если запрос был неудачным. Это можно сделать вызвав .raise_for_status():

import requests
from requests.exceptions import HTTPError

for url in ['https://api.github.com', 'https://api.github.com/invalid']:
    try:
        response = requests.get(url)

        # If the response was successful, no Exception will be raised
        response.raise_for_status()
    except HTTPError as http_err:
        print(f'HTTP error occurred: {http_err}')  # Python 3.6
    except Exception as err:
        print(f'Other error occurred: {err}')  # Python 3.6
    else:
        print('Success!')

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

Что еще прочитать: Если вы не знакомы f-строками в Python, то я призываю вас воспользоваться ими, так это отличный способ упростить отформатированные строки.

Теперь вы знаете многое о том, что делать с кодом ответа от сервера. Но когда вы делаете GET-запрос, вы редко заботитесь только об ответе сервера — обычно вы хотите увидеть больше.

Далее вы узнаете как просмотреть фактические данные, которые сервер отправил в теле ответа.

Content

Ответ на Get-запрос, в теле сообщения часто содержит некую ценную информацию, известную как «полезная нагрузка» («Payload»). Используя атрибуты и методы Response, вы можете просматривать payload в разных форматах.

Чтобы увидеть содержимое ответа в байтах, используйте .content:

response = requests.get('https://api.github.com')
response.content

Пока .content дает вам доступ к необработанным байтам полезной нагрузки ответа, вы можете захотеть преобразовать их в строку с использованием кодировки символов UTF-8. Response это сделает за вас, когда вы укажите .text:

response.text

Поскольку для декодирования байтов в строки требуется схема кодирования, Requests будет пытаться угадать кодировку на основе заголовков ответа. Вы можете указать кодировку явно, установив .encoding перед указанием .text:

response.encoding = 'utf-8' # Optional: requests infers this internally
response.text

Если вы посмотрите на ответ, то вы увидите, что на самом деле это последовательный JSON контент. Чтобы получить словарь, вы можете взять строку, которую получили из .text и десериализовать ее с помощью .json_loads(). Однако, более простой способ сделать это — использовать .json().

response.json()

Тип возвращаемого значения .json() — это словарь, поэтому вы можете получить доступ к значениям в объекте по ключу.

Вы можете делать многое с кодом состояний и телом сообщений. Но если вам нужна дополнительная информация, такая как метаданные о самом ответе, то вам нужно взглянуть на заголовки ответа.

Заголовки

Заголовки ответа могут дать вам полезную информацию, такую как тип ответа и ограничение по времени, в течение которого необходимо кэшировать ответ.
Чтобы посмотреть заголовки, укажите .headers:

response.headers

.headers возвращает похожий на словарь объект, позволяющий получить доступ к значениям объекта по ключу. Например, чтобы получить тип содержимого ответа, вы можете получить доступ к Content-Type:

response.headers['Content-Type']

Используя ключ content-type или Content-Type — вы получите одно и то же значение.

Теперь вы узнали основное о Response. Вы увидели его наиболее используемые атрибуты и методы в действии. Давайте сделаем шаг назад и посмотрим как изменяются ответы при настройке Get-запросов.

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

Один из распространенных способов настройки GET-запроса является передача значений через параметры строки в URL. Для этого, с помощью get() вы можете передать данные в params.


# Search GitHub's repositories for requests
response = requests.get(
    'https://api.github.com/search/repositories',
    params={'q': 'requests+language:python'},
)

# Inspect some attributes of the `requests` repository
json_response = response.json()
repository = json_response['items'][0]
print(f'Repository name: {repository["name"]}')  # Python 3.6+
print(f'Repository description: {repository["description"]}')  # Python 3.6+

Передав в словарь {'q': 'requests+language:python'} к параметру params в .get(), вы можете модифировать результаты, возвращаемые Search API.
Вы можете передать параметры в .get() в виде словаря, как вы это уже сделали, или в виде списка кортежей:

requests.get(
    'https://api.github.com/search/repositories',
    params=[('q', 'requests+language:python')],
)

Вы даже можете передать значение в байтах:

requests.get(
    'https://api.github.com/search/repositories',
    params=b'q=requests+language:python',
)

Строки запроса полезны для параметризации GET-запросов. Вы также можете изменить ваши запросы, добавив или изменив отправляемые вами заголовки.

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

Чтобы настроить заголовки, вы передаете словарь HTTP-заголовков в .get() с помощью параметра headers.
Например, вы можете изменить свой предыдущий поисковый запрос, чтобы выделить соответствующие результаты поиска, указав тип мультимедиа с текстовым соответствием в заголовке Accept:

import requests

response = requests.get(
    'https://api.github.com/search/repositories',
    params={'q': 'requests+language:python'},
    headers={'Accept': 'application/vnd.github.v3.text-match+json'},
)

# View the new `text-matches` array which provides information
# about your search term within the results
json_response = response.json()
repository = json_response['items'][0]
print(f'Text matches: {repository["text_matches"]}')

Заголовок Accept сообщает серверу, какие типы контента может обрабатывать ваше приложение.

В этом случае, вы используете значение заголовка application/vnd.github.v3.text-match+json, которое является собственным заголовком Github Accept, где в качестве содержимого используется формат JSON.

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

Другие методы HTTP

В стороне от GET, есть другие популярные методы включая: POST, DELETE, PUT, HEAD, PATCH и OPTIONS. Библиотека Requests предоставляет похожие возможности для работы с каждым из вышеперечисленных методов HTTP:

requests.post('https://httpbin.org/post', data={'key':'value'})
requests.put('https://httpbin.org/put', data={'key':'value'})
requests.delete('https://httpbin.org/delete')
requests.head('https://httpbin.org/get')
requests.patch('https://httpbin.org/patch', data={'key':'value'})
requests.options('https://httpbin.org/get')

Каждый вызов функции делает запрос в службу httpbin с использованием соответствующего метода HTTP. Для каждого метода вы можете проверить ответ:

response = requests.head('https://httpbin.org/get')
response.headers['Content-Type']

response = requests.delete('https://httpbin.org/delete')
json_response = response.json()
json_response['args']

Заголовки, тело ответов, коды состояния и многое другое возвращается в ответе для каждого метода. Далее вы познакомитесь с методами PUT, POST и PATCH и узнаете, чем они отличаются от других типов запросов.

Тело сообщения

Согласно спецификации HTTP — POST, PUT и менее распространенный метод PATCH, передают свои данные через тело сообщения, а не через параметры в строке запроса. Используя эти запросы, вы передатите полезную нагрузку в параметр data соответствующей функции.

Data принимает словарь, список кортежей, байтов или файлоподобный объект.
Вы можете захотеть адаптировать данные, которые отправляете в теле запроса, к конкретным потребностям сервиса, с которым взаимодействуете.

Например, если тип содержимого вашего запроса — application/x-www-form-urlencoded, вы можете отправить данные формы в виде словаря:

requests.post('https://httpbin.org/post', data={'key':'value'})

Вы также можете отправить данные в списке кортежей:

requests.post('https://httpbin.org/post', data=[('key', 'value')])

Если вам нужно отправить данные в формате JSON, вы можете использовать параметр json. Когда вы передаете данные через json, то Requests упорядочивает ваши данные и указывает за вас правильный заголовок Content-Type.

httpbin.org это прекрасный ресурс, созданный автором requests. Это сервис, который принимает тестовые запросы и возвращает данные о запросе. Например, вы можете использовать его для проверки POST запроса.

response = requests.post('https://httpbin.org/post', json={'key':'value'})
json_response = response.json()
json_response['data']

json_response['headers']['Content-Type']

В ответе вы увидите, что сервер получил данные вашего запроса и заголовки по мере их отправки.Requests предоставляет вам эту информацию в форме PreparedRequest.

Проверка вашего запроса

Когда вы делаете запрос, библиотека requests подготавливает данные, перед тем как отправить их на сервер. Подготовка включает в себя такие вещи, как сериализация JSON и проверка заголовков.

Вы можете посмотреть PreparedRequest получив доступ к .request:

response = requests.post('https://httpbin.org/post', json={'key':'value'})
response.request.headers['Content-Type']

response.request.url

response.request.body

Проверка PreparedRequest дает вам доступ ко всей информации о выполняемом запросе, такой как полезная нагрузка, URL, заголовки, аутенфикация и многое другое.

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

Аутентификация

Аутенфикация помогает сервисам понять кто вы. Как правило, вы предоставляете свои учетные данные на сервер, передавая данные через заголовок авторизации или пользовательский заголовок, определенный службой.
Все функции запроса, которые вы видели до этого момента, предоставляют параметр auth, который позволяет передавать вам свои учетные данные.

Одним из примеров API, которые требует аутентификации, является GitHub’s Authenticated User API. Это конечная точка предоставляет информацию о профиле аутентифицированного пользователя. Чтобы сделать запрос к Authenticated User API, вы можете передать свое имя пользователя и пароль в кортеже get():

from getpass import getpass
requests.get('https://api.github.com/user', auth=('username', getpass()))

Запрос будет успешно выполнен, если учетные данные, которые вы передали в кортеже auth верны. Если вы попробуете сделать запрос не указав учетные данные, то получите в ответ от сервера код состояния 401 Unauthorized.

Когда вы передаете имя пользователя и пароль в кортеже параметру auth, requests применяет учетные данные с использованием базовой схемы аутентификации доступа HTTP.
Таким образом, вы можете сделать тот же запрос, передав учетные данные, используя HTTPBasicAuth:

from requests.auth import HTTPBasicAuth
from getpass import getpass
requests.get(
    'https://api.github.com/user',
    auth=HTTPBasicAuth('username', getpass())
)

Вам не обязательно использовать HTTPBasicAuth, вы можете аутентифицироваться и другим способом. Requests из коробки предоставляют и другие методы аутентификации, такие как HTTPDigestAuth и HTTPProxyAuth.
Вы даже можете использовать свой собственный механизм аутентификации. Для этого, вы должны создать подкласс AuthBase, затем внедрить __call__():

from requests.auth import AuthBase

class TokenAuth(AuthBase):
    """Implements a custom authentication scheme."""

    def __init__(self, token):
        self.token = token

    def __call__(self, r):
        """Attach an API token to a custom auth header."""
        r.headers['X-TokenAuth'] = f'{self.token}'  # Python 3.6+
        return r

requests.get('https://httpbin.org/get', auth=TokenAuth('12345abcde-token'))

Здесь ваш пользовательский механизм TokenAuth получает токен, а затем включает этот токен в заголовок X-TokenAuth вашего запроса.

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

Пока вы думаете о безопасности, давайте рассмотрим работу с SSL-сертификатами в библиотеке requests.

Проверка SSL-сертификата

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

Хорошей новостью является то, что requests делает это за вас по умолчанию. Однако, в некоторых случаях, вы можете изменить это поведение.

Если вы хотите отключить проверку SSL-сертификата, вы передаете False в параметр verify функции requests:

requests.get('https://api.github.com', verify=False)

Библиотека requests даже предупреждает вас, когда вы делаете небезопасный запрос, чтобы помочь сохранить ваши данные в безопасности.

Производительность

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

Время ожидания

Когда вы отправляете запрос во внешнюю службу, вашей системе потребуется дождаться ответа, прежде чем двигаться дальше. Если ваше предложение слишком долго ожидает ответа — запросы к службе могут быть скопированы, пользовательский интерфейс может пострадать или фоновые задания могут зависнуть.

По умолчанию, requests будет ждать ответа до бесконечности, поэтому вы почти всегда должны указывать время ожидания. Чтобы установить время ожидания, используйте параметр timeout. Тайм-аут может быть целым числом или числом с плавающей запятой, представляющим количество секунд ожидания ответа:

requests.get('https://api.github.com', timeout=1)

requests.get('https://api.github.com', timeout=3.05)

В первом запросе, время ожидания истекает через одну секунду. Во втором — через 3,05 секунд.

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

requests.get('https://api.github.com', timeout=(2, 5))

Если запрос устанавливает соединение в течение 2 секунд и получает данные в течение 5 секунд после установки соединения, то ответ будет возвращен. Если время ожидания истекло — функция вызовет исключение Timeout:

import requests
from requests.exceptions import Timeout

try:
    response = requests.get('https://api.github.com', timeout=1)
except Timeout:
    print('The request timed out')
else:
    print('The request did not time out')

Ваша программа может перехватить исключение и ответить соответствующим образом.

Объект Session

До сих пор вы имели дело с интерфейсами API requests высокого уровня, такими как get() и post(). Эти функции являются абстракциями над тем, что происходит когда вы делаете свои запросы. Они скрывают детали реализации, такие как управление соединениями, так что вам не нужно беспокоиться о них.

Под этими абстракциями находится класс Session. Если вам необходимо настроить контроль над выполнением запросов и повысить производительность вашего приложения — вам может потребоваться использовать экземпляр Session напрямую.

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

import requests
from getpass import getpass

# By using a context manager, you can ensure the resources used by
# the session will be released after use
with requests.Session() as session:
    session.auth = ('username', getpass())

    # Instead of requests.get(), you'll use session.get()
    response = session.get('https://api.github.com/user')

# You can inspect the response just like you did before
print(response.headers)
print(response.json())

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

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

Максимальное количество попыток

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

Транспортные адаптеры позволяют вам определять набор конфигураций для каждой службы , с которой вы взаимодействуете. Например, вы хотите, чтобы все запросы к https://api.github.com, повторялись по три раза, прежде чем вызовется исключение ConnectionError. Вы должны сконструировать транспортный адаптер, установить его параметр max_retries и подключить его к существующему сеансу:

import requests
from requests.adapters import HTTPAdapter
from requests.exceptions import ConnectionError

github_adapter = HTTPAdapter(max_retries=3)

session = requests.Session()

# Use `github_adapter` for all requests to endpoints that start with this URL
session.mount('https://api.github.com', github_adapter)

try:
    session.get('https://api.github.com')
except ConnectionError as ce:
    print(ce)

Когда вы монтируете HTTPAdapter и github_adapter в SessionSession будет придерживаться этой конфигурации в каждом запросе к https://api.github.com.

Время ожидания, транспортные адаптеры и сеансы, предназначены для обеспечения эффективности вашего кода и отказоустойчивости приложения.

Заключение

Вы прошли долгий путь в изучении мощной библиотеки Requests в Python.

Теперь вы можете:

  • делать запросы, используя различные методы HTTP;
  • настроить свои запросы, изменив заголовки, тело сообщения и пр.;
  • проверить данные, которые вы отправляете на сервер и данные, которые получаете в ответ;
  • проверить SSL-сертификат;
  • эффективно использовать запросы с помощью max_retries, timeout, Session и транспортного адаптера.

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