Пособие по HTTP-запросам в Python и Web API

В этой статье мы рассмотрим, что такое API и, в частности, REST API. Также мы разберем, что такое HTTP-запросы и какими они бывают. Кроме того, мы рассмотрим основные компоненты библиотеки requests и предоставим несколько примеров кода, которые помогут вам начать работу с ней.

Содержание

Что такое API

Хотите скачать книги по Python в 2 клика? Тогда вам в наш телеграм канал PythonBooks 

Интерфейс прикладного программирования (API) — это посредник между программами; его работа заключается в том, чтобы разрешать приложениям взаимодействовать друг с другом. Эти неявные посредники постоянно появляются в нашей повседневной жизни, знаете вы об этом или нет. Например, если сегодня вы отправляли сообщение в мессенджере, то вы использовали API.

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

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

HTTP-запросы в Python делаются при помощи библиотеки requests. Это практически стандарт. Правда, в Python также есть встроенная библиотека urllib, но питонисты, как правило, предпочитают API библиотеки requests из-за ее удобочитаемости и того факта, что она полностью поддерживает REST API. Этого мы коснемся чуть позже.

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

REST API

Мы определили, что API — это посредник между программами. Иначе его можно представить как тип программного интерфейса, который предоставляет другим приложениям доступ к определенным данным и методам.

Одной из самых популярных архитектур, используемых для создания API, является шаблон REpresentational State Transfer (REST). Архитектура REST позволяет реализовывать приложения на стороне клиента и на стороне сервера независимо друг от друга. Это означает, что код на любой стороне можно изменить, не беспокоясь о том, как это изменение повлияет на другую сторону.

Существует набор рекомендаций, предназначенных для упрощения обмена данными между программами, что делает процесс доступа к данным более простым и логичным. Не беспокойтесь, если вы не знаете этих рекомендаций: чтобы начать работу, вам не нужно знать их. Для этого вам нужно знать, как данные предоставляются службами REST.

Данные из веб-служб REST доступны в Интернете через общедоступный URL-адрес, к которому можно обращаться, отправляя HTTP-запросы.

HTTP-запросы

Вернемся к нашей аналогии с рестораном. Чтобы заказать еду в ресторане, вы говорите официанту, чего бы вам хотелось. Затем официант передает вашу просьбу шеф-повару, который готовит еду и передает ее официанту, чтобы тот вернулся с ней к вам. То есть шеф-повар не будет готовить вам еду, пока не получит ваш запрос.

Интерфейсы REST API точно такие же: они прослушивают HTTP-запросы, прежде чем предпринимать какие-либо действия. Протокол HTTP — это то, что определяет набор запросов, чтобы сообщить API, какие операции выполнять для данного ресурса. Он указывает, как взаимодействовать с ресурсами, расположенными в данной конечной точке.

С REST API обычно используются следующие HTTP-запросы:

HTTP-запросыИх описание
GETПолучить данные
POSTСоздать данные
PUTОбновить существующие данные
PATCHЧастично обновить существующие данные
DELETEУдалить данные

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

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

Конечные точки

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

Чтобы добавить больше контекста, REST API предоставляют набор общедоступных URL-адресов, которые могут быть запрошены клиентскими приложениями для доступа к ресурсам веб-службы. Общедоступные URL-адреса, предоставляемые REST API, называются конечными точками.

Использование Python для работы с API

API библиотеки requests позволяет разработчикам писать код для взаимодействия с REST API. Это дает им возможность отправлять HTTP-запросы с помощью Python, не беспокоясь о сложностях, которые обычно возникают при выполнении таких задач (например, ручное добавление строк запроса к URL-адресам, кодирование форм данных PUT и POST и т. д.).

Несмотря на то, что библиотека requests считается де-факто стандартом для создания HTTP-запросов в Python, она не является частью стандартной библиотеки Python и ее необходимо установить.

Наиболее естественно сделать это при помощи менеджера пакетов pip:

python -m pip install requests

Этот код представляет собой команду, которую можно запустить в терминале или в командной строке. Флаг -m указывает интерпретатору Python запустить модуль pip как скрипт, а request — это имя устанавливаемого пакета. После выполнения этой команды библиотека requests будет установлена ​​и доступна для использования в среде Python.

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

Теперь, когда у нас установлен модуль requests, давайте посмотрим, как он работает.

Делаем GET-запрос

Мы уже говорили, что GET — это один из наиболее распространенных HTTP-запросов, с которыми вы столкнетесь при работе с REST API. Он позволяет вам (то есть клиенту) получать данные с веб-серверов.

Важно отметить, что GET — это операция только для чтения. Это означает, что она подходит только для доступа к существующим ресурсам, но не должна использоваться для их изменения.

Чтобы продемонстрировать, как работает модуль запроса, мы будем использовать JSONPlaceholder — свободно доступный API, используемый для тестирования и прототипирования.

Рассмотрим следующий код:

import requests

# Конечная точка API
url = "https://jsonplaceholder.typicode.com/posts/1"

# GET-запрос к API
response = requests.get(url)

# Выводим ответ в консоль
response_json = response.json()
print(response_json)

# Результат
"""
{'userId': 1, 'id': 1, 'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', 'body': 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto'}
"""

В приведенном выше коде мы сделали следующее:

  1. Определили конечную точка API для извлечения данных.
  2. Для получения данных из определенной конечной точки использовали метод request.get(url).
  3. Мы использовали метод response.json() для хранения данных ответа в объекте словаря. Обратите внимание, что это работает только потому, что результат записывается в формате JSON — иначе бы возникла ошибка.
  4. Последний шаг — выводим данные ответа в консоль в формате JSON.

Код состояния, возвращенный нам API, мы можем проверить следующим образом:

# Print status code from original response (not JSON)
print(response.status_code)

# Результат
"""
200
"""

Результат показывает, что код состояния равен 200. Это значит, что запрос выполнен успешно.

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

# Конечная точка API
url = "https://jsonplaceholder.typicode.com/posts/"

# Добавляем переменную payload
payload = {"id": [1, 2, 3], "userId":1}

# GET-запрос к API
response = requests.get(url, params=payload)

# Выводим ответ в консоль
response_json = response.json()

for i in response_json:
    print(i, "\n")

# Результат
"""
{'userId': 1, 'id': 1, 'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', 'body': 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto'}

{'userId': 1, 'id': 2, 'title': 'qui est esse', 'body': 'est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla'}

{'userId': 1, 'id': 3, 'title': 'ea molestias quasi exercitationem repellat qui ipsa sit aut', 'body': 'et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut'}
"""

Вот что мы изменили:

  1. Изменили конечную точку API. Обратите внимание, что в конце больше нет 1.
  2. Задали переменную payload в виде словаря.
  3. Передали переменную payload в аргумент param метода request.get().
  4. Нам вернулся объект в виде списка. Поэтому мы его проитерировали при помощи цикла for и вывели каждый его элемент в новой строке.

Делаем POST-запрос

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

# Определяем новые данные
new_data = {
    "userID": 1,
    "id": 1,
    "title": "Making a POST request",
    "body": "This is the data we created."
}

# Конечная точка API для связи
url_post = "https://jsonplaceholder.typicode.com/posts"

# POST-запрос к API
post_response = requests.post(url_post, json=new_data)

# Выводим ответ в консоль
post_response_json = post_response.json()
print(post_response_json)

# Результат
"""
{'userID': 1, 'id': 101, 'title': 'Making a POST request', 'body': 'This is the data we created.'}
"""

В приведенном выше коде мы сделали следующее:

  1. Создали новые данные в виде словаря, который мы хотим добавить в JSONPlaceholder API.
  2. Определили конечную точку для помещения новых данных при помощи POST-запроса.
  3. Отправили POST-запрос при помощи метода request.post(). Обратите внимание, что в методе post() мы установили параметр json. Мы сделали это, чтобы в явном виде сообщить API, что мы отправляем данные в формате JSON.
  4. Использовали метод response.json() для сохранения данных ответа в объекте словаря.
  5. Последний шаг — выводим данные ответа в консоль в формате JSON.

Но минуточку!

Прежде чем вы прочитаете следующий фрагмент кода, потратьте 20 секунд на то, чтобы подумать о том, какой код состояния мы можем ожидать от API.

Имейте ввиду, что на этот раз мы создали новые данные, а не просто их извлекли.

Ок, вот код ответа от сервера:

# Print status code from original response (not JSON)
print(post_response.status_code)

# Результат:
"""
201
"""

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

А вы угадали ответ?

Продвинутые темы

Запросы аутентификации

До этого момента взаимодействие с REST API было довольно простым. API сервера JSONPlaceholder не требует никакой аутентификации, чтобы вы могли начать с ним взаимодействовать. Но зачастую бывает, что REST API требует аутентификацию, прежде чем будет предоставлен доступ к определенным конечным точкам. Особенно это касается случаев, когда вы имеете дело с конфиденциальными данными.

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

Вот простой способ сделать это с использованием модуля requests Python:

from requests.auth import HTTPBasicAuth

private_url = "https://api.github.com/user"
github_username = "username"
token = "token"

private_url_response = requests.get(
    url=private_url,
    auth=HTTPBasicAuth(github_username, token)
)

private_url_response.status_code

# Результат:
"""
200
"""

В данном коде мы сделали следующее:

  1. Импортировали объект HTTPBasicAuth из request.auth. Этот объект прикрепляет базовую аутентификацию HTTP к нашему объекту запроса — это по сути то же самое, что и ввод вашего имени пользователя и пароля на сайте.
  2. Задали конечную точку доступа в переменной private_url.
  3. Создали переменную с именем пользователя GitHub (мы анонимизировали имя пользователя для конфиденциальности).
  4. Создали переменную token с личным токеном доступа для аутентификации в Github.
  5. Получили данные из нашей конечной точки и сохранили их в переменной private_url_response.
  6. Отобразили код состояния, который говорит нам о том, что запрос был выполнен успешно.

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

Бывают случаи, когда запросы к API выполняются не так, как ожидалось. На это может влиять множество факторов, как на стороне клиента, так и на стороне сервера. Независимо от причины, результат всегда один и тот же: запрос не выполняется.

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

Давайте опять вернемся к API сервера JSONPlaceholder. Мы начнем с написания кода, а затем объясним, что происходит.

# В конечной точке "postz" вместо "posts". Была сделана преднамеренная опечатка.
url = "https://jsonplaceholder.typicode.com/postz"

# Attempt to GET data from provided endpoint
try:
    response = requests.get(url)
    response.raise_for_status()
# If the request fails (404) then print the error.
except requests.exceptions.HTTPError as error:
  print(error)

# Результат:
"""
404 Client Error: Not Found for url: https://jsonplaceholder.typicode.com/postz
"""

В данном коде мы сделали следующее:

  1. Определена конечная точка сервера JSONPlaceholder для извлечения данных, но мы сделали преднамеренную опечатку при построении URL-адреса — это должно вызвать ошибку 404.
  2. Использована встроенная обработка исключений Python. try и except перехватывают любые ошибки, возникающие при попытке обратиться к серверу JSONPlaceholder. Обратите внимание, что метод raise_for_status() используется для возврата объекта HTTPError при возникновении ошибки во время процесса запроса.
  3. В консоль выводится возникшая ошибка.

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

Работа со слишком большим количеством переадресаций (редиректов)

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

Для решения этой проблемы модуль requests предоставляет объект TooManyRedirects, который работает следующим образом:

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

url = "https://jsonplaceholder.typicode.com/posts"

try:
  response = requests.get(url)
  response.raise_for_status()
except requests.exceptions.TooManyRedirects as error:
  print(error)

Если переадресаций слишком много, метод raise_for_status() вызовет исключение TooManyRedirects, которое перехватывается блоком исключения и будет выведено сообщение об ошибке.

Вы также можете установить максимальное количество переадресаций в качестве параметра вашего метода HTTP-запроса:

# Решение 2
url = "https://jsonplaceholder.typicode.com/posts"
session = requests.Session()
session.max_redirects = 3
response = session.get(url)

Другой вариант — полностью отключить переадресацию:

# Решение 3
url = "https://jsonplaceholder.typicode.com/posts"
session = requests.Session()
session.allow_redirects = False
response = session.get(url)

Строка session.allow_redirects = False устанавливает для атрибута allow_redirects объекта Session значение False, что означает, что сеанс не будет следовать никаким переадресациям, которые могут быть возвращены сервером.

Ошибки подключения

Это еще один тип ошибок, с которыми вы можете столкнуться при отправке запросов на сервер. Существует несколько причин, по которым вы можете не получить ответ от сервера. Например, сбой DNS, отказ в подключении, проблемы с подключением к Интернету и так далее. Но результат одинаков: возникает ошибка подключения.

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

Вот как будет выглядеть код:

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

url = "https://jsonplaceholder.typicode.com/posts"

try:
  response = requests.get(url)
except requests.ConnectionError as error:
  print(error)

Таймаут

Когда API сервера принимает соединение, но не может выполнить ваш запрос в отведенное время, вы получите так называемую timeout error (ошибку таймаута).

Мы продемонстрируем, как справиться с этим случаем, установив для параметра timeout в методе request.get() очень маленькое число. Это вызовет ошибку, и мы ее обработаем с помощью объекта request.Timeout.

url = "https://jsonplaceholder.typicode.com/posts"

try:
  response = requests.get(url, timeout=0.0001)
except requests.Timeout as error:
  print(error)

Самый простой путь для обхода ошибок таймаута — установить большее число в параметр timeout. Другие решения могут включать в себя оптимизацию ваших запросов, включение цикла повторных попыток в ваши сценарии или выполнение асинхронных вызовов API.

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

Заключение

В данной статье мы рассмотрели, что такое API, и изучили общую архитектуру API, называемую REST. Мы также рассмотрели HTTP-запросы и то, как мы можем использовать библиотеку requests языка Python для взаимодействия с веб-сервисами.

Перевод статьи Web APIs, Python Requests & Performing an HTTP Request in Python Tutorial.