Как отправлять запросы к api
Руководство по созданию API-запросов в Python
Python сейчас переживает свой период возрождения. Собственно, он и не умирал, но сейчас степень его использования высока, как никогда раньше. Именно на этот язык полагаются разработчики машинного обучения и специалисты по обработке данных, а большая часть экосистемы веб-разработки вокруг этого языка продолжает расти.
Один из аспектов, влияющих на все три эти специализации, — мощные преимущества API. Сбор данных и подключение к внешним сервисам — важная часть любого языка. В этой статье мы рассмотрим основные библиотеки для выполнения HTTP-запросов, а также некоторые распространенные варианты их использования, позволяющие подключаться к API в Python. Но сначала следует ответить на один важный вопрос:
Подходит ли Python для API?
Это может показаться странным вопросом, но, учитывая засилье в вебе Node.js и Ruby, вы можете подумать, что Python не так хорош для создания API-запросов. Но это не так. На самом деле Python тоже давно присутствует в вебе, особенно если принять во внимание его библиотеки Flask и Django.
Поскольку Python предоставляет мощные и доступные инструменты для работы с данными, имеет смысл также использовать его и для получения самих источников данных. Для этого и служат запросы к API. Давайте начнем с самой популярной библиотеки Python под названием Requests.
Библиотека Requests
Requests — это широко известная библиотека, которую разработчики используют для выполнения API-запросов в Python. Она предлагает интерфейс для синхронного выполнения HTTP-запросов. Давайте рассмотрим несколько распространенных типов запросов, которые вы можете делать с помощью этой библиотеки. В дальнейшем мы будем предполагать, что эта библиотека у вас уже установлена. Для установки вы можете использовать документацию библиотеки, но сам процесс очень прост.
Библиотеку также нужно импортировать:
Распространенные типы API-запросов в библиотеке Requests
Наиболее простой GET-запрос интуитивно понятен:
Этот запрос был довольно прост. Давайте теперь рассмотрим более сложные запросы. Часто документация по API требует, чтобы вы передавали параметры запроса в конкретную конечную точку. Чтобы это сделать, мы можем передать параметры запроса в метод get в качестве второго аргумента.
Переменная response содержит данные, возвращаемые API в качестве ответа на наш запрос. Есть три основных способа доступа к этим данным:
Помимо параметров запроса, мы также можем передавать в запрос заголовки:
Здесь мы передаем в качестве аргумента заголовок в виде словаря Python.
Последний тип API-запроса, который мы сейчас рассмотрим — это полнофункциональный POST-запрос с аутентификацией. Этот пример объединит два предыдущих, здесь мы передадим в качестве аргументов и заголовок, и данные.
Здесь мы отправляем данные из переменной payload в формате словаря Python. Для большинства современных API нам часто требуется отправлять данные в формате JSON. В следующем примере мы используем встроенный json-конвертер из библиотеки Requests.
Библиотека Requests отлично подходит для выполнения синхронных API-запросов, но иногда вашему приложению нужно выполнять асинхронные запросы. Для этого мы можем использовать асинхронную HTTP-библиотеку aiohttp.
Библиотека aiohttp
Для выполнения асинхронных HTTP-запросов вам необходимо воспользоваться некоторыми новыми функциями Python 3. Хотя у библиотеки Requests есть различные дополнения и плагины для асинхронного программирования, одной из наиболее популярных библиотек для этого является библиотека aiohttp. Используя ее вместе с библиотекой acincio, мы можем эффективно исполнять асинхронные запросы. Код будет немного сложнее, но асинхронные запросы сами по себе предоставляют большую свободу действий.
Для начала установим библиотеку aiohttp:
Распространенные типы API-запросов в библиотеке aiohttp
Как и раньше, начнем с GET-запроса. Для начала импортируем обе библиотеки и определим функцию main() как асинхронную.
В этом коде мы выполняем следующие шаги:
Если вы никогда раньше не работали с асинхронными методами в Python, это может показаться странным и сложным по сравнению с предыдущими примерами. Создатели библиотеки aiohttp рекомендуют устанавливать один сеанс для каждого приложения и открывать / закрывать соединения в этом сеансе. Чтобы сделать наши примеры самодостаточными, мы составили примеры в менее эффективном формате.
Далее давайте рассмотрим полнофункциональный POST-запрос с заголовками аутентификации, как мы сделали в примере для библиотеки Requests.
Между этим примером и предыдущим есть несколько различий:
При помощи этих двух фрагментов кода мы можем выполнять большинство распространенных задач, связанных с API. Дополнительные функции, такие как загрузка файлов и данные формы, можно найти в документации для разработчиков aiohttp.
Некоторые дополнительные библиотеки
Хотя библиотека Requests и является самой популярной и универсальной, в некоторых особых случаях вы можете найти полезными и другие библиотеки.
Кратко: запросы к API и разбор XML-ответов. Python
Этот пост предназначен в первую очередь для новичков в разработке, впервые столкнувшихся с необходимостью отправить post/get запросы к какому-нибудь API и проанализировать полученный в XML ответ. Постаралась собрать необходимы минимум в одном месте.
1. Непосредственно API
Оно может быть от ваших коллег, партнеров, заказчиков, сторонних сервисов. Разной степени готовности и актуальности. Но у него как правило есть заголовки запросов, параметры запросов, параметры ответа и статус-коды ответов. Например:
Воспользуемся модулем CaseInsensitiveDict из requests.structures для того что бы собирать заголовки запроса в привычные headers. Параметры запроса определим в параметре files. Тогда для метода с первого скрина post-запрос будет выглядеть следующим образом:
Вариант с ответом в json в этой статье разбирать не буду, по нему написано очень много материалов, поэтому остановлюсь подробнее на XML.
2. XML. Чтение и разбор
После получения XML-портянки на этапе отладки не лишним будет проверить визуально данные в ней. Например с помощью сервиса https://jsonformatter.org/xml-parser.
В приходящем респонсе байтовая кодировка(необходимая для раскладывания xml по дереву) находится только в атрибуте content. перепишем для дальнейшей работы его в отдельную переменную responce_xml_content.
В дебагере очень похоже выглядит атрибут text у полученного responce, но он там существует как utf-8.
Большая часть мануалов по парсингу xml написана под чтение из файла и под библиотеку etree. И метод для строки из переменной fromstring в каждом классе работает несколько по разному.
Поэтому оптимальным считаю использование etree из модуля lxml. С ним проверка существования пользователя get-запросом и добавление пользователя post-запросом выглядит лаконично.
3. Анализ XML. XPath или Bs4
Примечательно что родители знают о своих детях, но дети не знают о своих родителях. Напрямую, задав путь до проверки атрибута элемента на пятом уровне вложенности, в случае если такой найден, забрать атрибут с его третьего уровня вложенности не получится.
Один из немногих мануалов по тому как с ним обращаться http://www.k-press.ru/cs/2001/2/XPath/XPath4.asp
Например для того что бы достать из списка пользователей id пользователя по искомому email, находящегося в атрибуте value в элементе в котором атрибут name со значением email выражение будет выглядеть так:
Есть еще вариант использовать для этого Bs4. Тогда тот же поиск будет выглядеть так:
4. Размещаем эти запросы на flask.
Рекоммендуется использовать стандартную структуру страниц.
В api.py расположим основной обработчик страниц. Для обработки post-запроса с json-ом и примером формы. Для валидации самым лаконичным решением будет pydantic,из которого потребуется BaseModel, ValidationError, validator
Если потребуется styles.css то располагаться ему следует в /static/css.
Форму регистрации сделаем например в таком виде
После чего мы можем например отправлять post-запросы к нашему сервису, таким же образом, curl-ом, через postman или руками заполняя формы на веб-странице.
Как сделать запрос к API?
API InSales позволяет работать с бэк-офисом магазина, используя HTTP-запросы. При помощи API можно получать, добавлять, изменять и удалять информацию о различных объектах (например, товарах, категориях, дополнительных полях и т.д.)
Предполагается, что работа с API организована на внешнем сервере (НЕ на сервере платформы), который отправляет к серверу платформы запросы определенного типа. Для удобства в этой статье будет рассмотрена отправка запросов при помощи приложения Postman. В качестве альтернативы можно использовать Advanced REST Client.
Все доступные запросы в форматах XML и JSON описаны на api.insales.ru. Типовые запросы в формате XML с более подробным описанием можно найти на api.insales.ru.
Ограничения
На платформе присутствует ограничение в 500 запросов к API в течение 5 минут. Время рассчитывается с момента первого запроса в серии. Количество выполненных запросов в текущем промежутке времени передается в HTTP-заголовке API-Usage-Limit (например, API-Usage-Limit: 1/500).
При достижении лимита доступ к API ограничивается до окончания текущих 5 минут. При этом в HTTP-заголовке Retry-After передается время в секундах до восстановления доступа.
Авторизация
Для отправки запросов к API необходимо создать ключ доступа.
Внимание! Не используйте логин-пароль от бэк-офиса для работы с API, иначе ваш аккаунт может быть заблокирован.
Далее в статье мы будем использовать созданные логин (идентификатор) и пароль. Их можно указывать как в адресе запроса:
Так и в виде HTTP-заголовка Authorization:
Authorization: Basic ZGlnaXRhbC1nb29kczpjNTFjOTA3MDdhMTNjZTNmZmYyMTNhZmJiNWNkMTI3MA==
Вместо shop-47483-27.myinsales.ru необходимо использовать технический или обычный домен вашего магазина. Его можно взять, например, из строки «Пример URL» около выданного ключа доступа.
Общие принципы
API InSales принимает POST, GET, PUT и DELETE-запросы в зависимости от выполняемого действия. Тип и адрес запроса для требуемого действия можно найти на api.insales.ru.
Текст самого запроса должен соответствовать формату XML или JSON. В зависимости от формата меняется адрес запроса и значение HTTP-заголовка Content-Type.
Для XML необходим Content-Type: application/xml
На скриншоте программы Postman показан выбранный тип запроса, адрес и заголовки. После нажатия кнопки Send появится ответ сервера InSales.
Примеры XML-запросов
Рассмотрим GET-запрос к товару с id 143570988. Такой запрос не требует передачи серверу каких-либо параметров. В качестве адреса необходимо указать https://логин:пароль@shop-47483-27.myinsales.ru/admin/products/143570988.xml
В ответ сервер пришлёт всю доступную информацию об этом товаре.
PUT-запрос служит для обновления данных выбранного объекта. Для обновления названия того же товара с id 143570988 потребуется отправить запрос с телом
В ответ сервер пришлёт обновлённую информацию о товаре (в том числе новое название). Соответственно, в бэк-офисе также будет отображаться новое название товара. В ответе виден код 200, что означает успешное выполнение операции.
Примеры JSON-запросов
Запросы в формате JSON отличаются адресом и заголовком Content-Type.
Тело PUT-запроса на обновление названия товара выглядит следующим образом:
В этом руководстве написано клиентское приложение, использующее следующий веб-API:
| Действие | Метод HTTP | Относительный URI |
|---|---|---|
| Получение сведений о продукте по идентификатору | GET | идентификатор /АПИ/Продуктс/ |
| Создать продукт | POST | /апи/продуктс |
| Обновить продукт | PUT | идентификатор /АПИ/Продуктс/ |
| Удалить продукт | DELETE | идентификатор /АПИ/Продуктс/ |
Сведения о том, как реализовать этот API с веб-API ASP.NET, см. в разделе Создание веб-API, поддерживающего операции CRUD.
Примечание. При передаче базовых URL-адресов и относительных URI в виде жестко заданных значений следует учитывать правила использования HttpClient API. HttpClient.BaseAddress Для свойства необходимо задать адрес с завершающей косой чертой ( / ). Например, при передаче в метод жестко запрограммированных URI ресурсов HttpClient.GetAsync не включайте в начале косую черту вперед. Чтобы получить объект Product по идентификатору:
Создание консольного приложения
В Visual Studio создайте новое консольное приложение Windows с именем хттпклиентсампле и вставьте следующий код:
Приведенный выше код является полноценным клиентским приложением.
Установка клиентских библиотек веб-API
Используйте диспетчер пакетов NuGet для установки пакета клиентских библиотек веб-API.
В меню Инструменты выберите Диспетчер пакетов NuGet > Консоль диспетчера пакетов. В консоли диспетчера пакетов (PMC) введите следующую команду:
Предыдущая команда добавляет в проект следующие пакеты NuGet:
Добавление класса модели
Проверьте класс Product :
Этот класс соответствует модели данных, используемой веб-API. Приложение может использовать HttpClient для чтения Product экземпляра из HTTP-ответа. Приложению не нужно писать код десериализации.
Создание и инициализация HttpClient
Изучите статическое свойство HttpClient :
HttpClient предназначен для однократного создания и повторного использования в течение всего жизненного цикла приложения. Следующие условия могут привести к ошибкам SocketException :
Создание нового экземпляра HttpClient для каждого запроса может исчерпать доступные сокеты.
Следующий код инициализирует экземпляр HttpClient :
Отправка запроса GET для получения ресурса
Следующий код отправляет запрос GET для продукта:
Media-Type модулей форматирования для десериализации
Когда реадасасинк вызывается без параметров, он использует набор модулей форматирования мультимедиа по умолчанию для чтения текста ответа. Модули форматирования по умолчанию поддерживают данные JSON, XML и кодируются в формате URL.
Отправка запроса POST для создания ресурса
Следующий код отправляет запрос POST, содержащий Product экземпляр в формате JSON:
Метод постасжсонасинк :
Если запрос будет выполнен, выполните следующие действия:
Отправка запроса на размещение для обновления ресурса
Следующий код отправляет запрос на размещение для обновления продукта:
Метод путасжсонасинк работает так же, как постасжсонасинк, за исключением того, что он отправляет запрос на размещение вместо POST.
Отправка запроса на удаление для удаления ресурса
Следующий код отправляет запрос на удаление для удаления продукта:
Как и в случае GET, запрос на удаление не содержит текст запроса. Не нужно указывать формат JSON или XML с помощью DELETE.
Тестирование примера
Чтобы протестировать клиентское приложение, выполните следующие действия.
Скачайте и запустите серверное приложение. Указания по скачиванию. Убедитесь, что серверное приложение работает. Например, http://localhost:64195/api/products должен возвращать список продуктов.
Задайте базовый URI для HTTP-запросов. Измените номер порта на порт, используемый в серверном приложении.
Запустите клиентское приложение. Выводятся следующие результаты:
Принципы построения REST JSON API

Зачем
Надеюсь, читающий уже понимает, зачем ему вообще нужен именно REST api, а не какой-нибудь монстр типа SOAP. Вопрос в том, зачем соблюдать какие-то стандарты и практики, если браузеры вроде бы позволяют делать что хочешь.
Структура запросов и ответов
Любой http-запрос начинается со строки
где METHOD — это метод доступа (GET, PUT и т.д.), а URI — адрес запрашиваемого ресурса.
В начале запроса идут заголовки — просто текстовые строки вида key: value
Затем передаётся пустая строка, означающая конец секции заголовков, и затем — тело запроса, если оно есть.
В ответе сначала передаётся строка с версией http, кодом и строковым статусом ответа (например HTTP/1.1 200 OK ), далее текстовые заголовки ответа, потом пустая строка, потом тело ответа.
Тут вроде всё просто.
Кодирование запросов и ответов
Кодировка для всех и запросов, и ответов — UTF-8 и только UTF-8, т.к. некоторые, кхм, «браузеры» имеют привычку игнорировать содержимое заголовка charset.
Использование кодов символов и html-сущностей не допускается, т.е. режим JSON_UNESCAPED_UNICODE обязателен. Не все клиенты знают всю таблицу html сущностей (типа каких-нибудь ù ), да и при чём тут html. Не все клиенты готовы/хотят заниматься перекодированием \uXXXX; и &#XX;. Плюс возможны «весёлые» ситуации с избыточным экранированием или пропаданием слэшей и амперсандов.
Все данные, кроме URI и двоичных файлов, передаются в формате JSON. Обратите внимание, что далеко не всякий валидный javascript код является валидным JSON.
В частности, для строк используются только двойные кавычки. Одинарные кавычки в json-данных, хотя и допустимы в «обычном» javascript, могут вызвать непредсказуемые плохо отлавливаемые баги.
В запросах обязательно указывается заголовок
Вызовы к API отличаются от прочих вызовов (например, обычной загрузки html страницы по данному URI) именно по наличию application/json в Accept.
В ответах 2хх с непустым телом обязательно наличие заголовка ответа
При наличии тела запроса также обязателен заголовок запроса
либо, при загрузке файлов,
и далее, в первой части
после чего для каждого файла
Если вы используете защиту от CSRF (а лучше бы вам её использовать), то удобнее передавать CSRF-токен в отдельном заголовке (типа X-CSRF-Token) для всех запросов, а не внедрять вручную в каждый запрос. Хранить CSRF токен в куках плохо по той причине, что куки можно украсть, в чём собственно и состоит суть CSRF атаки.
Структура URI
Нагородить можно всякое, но лучшая практика — чтобы все URI имели вид
ну, или если у вас api лежит в какой-то папке,
Для разбора части URI до знака вопроса можно использовать регулярку
Ведущий слэш обязателен, т.к. неизвестно, с какого URL будет осуществлён запрос.
Методы HTTP
GET /:entity/:id — getById
В случае успеха сервер возвращает 200 OK с полями объекта в формате JSON в теле ответа (без дополнительного оборачивания в какой-либо объект)
В случае, если объект с такими id не существует, сервер возвращает 404 Not Found
В ответе обязательно должны быть заголовки, касающиеся политики кэширования, т.к. браузеры активно кешируют GET и HEAD запросы. При остутствии какой-либо политики управления кэшем должно быть:
GET /:entity[?param1=. ¶m2=. ] — списочный get
Простой случай: в случае успеха сервер возвращает 200 OK с массивом объектов в формате JSON в теле ответа (т.е. ответ начинается с [ и заканчивается ] ).
Если массив получился пустой, всё равно вовзращается 200 OK с пустым масивом [] в теле ответа.
Более сложный вариант: возвращается объект, в одном из полей которого — искомый массив. В остальных полях — данные о пагинации, фильтры, счётчики и пр. Только держите это консистентным по всем api.
HEAD /:entity[/:id] — запрос заголовков
Полный аналог GET с таким же URI, но не возвращает тело ответа, а только HTTP-заголовки.
Реализация поддержки HEAD запросов веб-сервером обязательна.
Активно используется браузерами в качестве автоматических pre-flight запросов перед выполнением потенциально опасных, по их мнению, операций. Например, браузер Chrome активно кидается head-запросами для получения политик CORS при кросс-доменных операциях (виджеты и пр). При этом ошибка обработки такого head-запроса приведёт к тому, что основной запрос вообще не будет выполнен браузером.
Может использоваться для проверки существования объекта без его передачи (например, для больших объектов типа мультимедиа-файлов).
POST /:entity — создаёт новый объект типа :entity
В теле запроса должны быть перечислены поля объекта в формате JSON без дополнительного заворачивания, т.е.
В случае успеха сервер должен возвращать 201 Created с пустым телом, но с дополнительным заголовком
указывающим на месторасположение созданного объекта.
Возвращать тело ответа чаще всего не требуется, так как у клиента есть все необходимые данные, а id созданного объекта он может получить из Location.
Также метод POST используется для удалённого вызова процедур (RPC), в этом случае ответ будет иметь статус 200 OK и результаты в теле. Вообще смешивать REST и RPC в одном api — идея сомнительная, но всякое бывает.
Единственный неидемпотентный некешируемый метод, т.е. повтор двух одинаковых POST запросов создаст два одинаковых объекта.
PUT /:entity/:id — изменяет объект целиком
В запросе должны содержаться все поля изменяемого объекта в формате JSON.
В случае успеха должен возвращать 204 No Data с пустым телом, т.к. у клиента есть все необходимые данные.
Идемпотентный запрос, т.е. повторный PUT с таким же телом не приводит к каким-либо изменениям в БД.
PATCH /:entity/:id — изменяет отдельные поля объекта
В запросе должны быть перечислены только поля, подлежащие изменению.
В случае успеха возвращает 200 OK с телом, аналогичным запросу getById, со всеми полями изменённого объекта.
Используется с осторожностью, т.к. два параллельных PATCH от двух разных клиентов могут привести объект в невалидное состояние.
DELETE /:entity/:id — удаляет объект, если он существует.
В случае успеха возвращает 204 No Data с пустым телом, т.к. возвращать уже нечего.
Идемпотентный запрос, т.е. повторный DELETE с таким же адресом не приводит к ошибке 404.
OPTIONS /:entity[/:id]
Получает список методов, доступных по данному URI.
Сервер должен ответить 200 OK с дополнительным заголовком
Некешиуремый необязательный метод.
Обработка ошибок
Возвращаемые ошибки передаются с сервера на клиент как ответы со статусами 4хх (ошибка клиента) или 5хх (ошибка сервера). При этом описание ошибки, если оно есть, приводится в теле ответа в формате text/plain (без всякого JSON). Соответственно, передаётся заголовок ответа
Использовать html для оформления сообщений об ошибках в api — так себе идея, будут проблемы журналированием и т.д. Предполагается, что клиент способен сам красиво оформить сообщение об ошибке для пользователя.
При выборе конкретных кодов ошибок не следует слишком увлекаться и пытаться применить существующие коды только потому, что название кажется подходящим. У многих кодов есть дополнительные требования к наличию определённых заголовков и специальная обработка браузерами. Например, код 401 запускает HTTP-аутентификацию, которая будет странно смотреться в каком-нибудь приложении на react или electron.
UPD по мотивам комментариев. Клиенты у вас будут разные: не только веб и мобильные приложения, но и такие штуки, как запускалка интеграционных тестов (CI), балансировщик нагрузки или система мониторинга у админов. Использование или неиспользование того или иного статуса ошибки определяется тем, будет ли он полезен хоть какому-то клиенту (т.е. этот клиент сможет предпринять какие-то действия конкретно по этому коду) и, наоборот, не будет ли проблем у какого-то из клиентов из-за неиспользования вами этого кода. Придумать реальный use-case, когда реакция клиента будет различаться в зависимости от 404 или 410, довольно сложно. При этом отличий 404 от 200 или 500 — вагон и телега.
400 Bad Request
Универсальный код ошибки, если серверу непонятен запрос от клиента.
403 Forbidden
Возвращается, если операция запрещена для текущего пользователя. Если у оператора есть учётка с более высокими правами, он должен перелогиниться самостоятельно. См. также 419
404 Not Found
Возвращается, если в запросе был указан неизвестный entity или id несуществующего объекта.
Списочные методы get не должны возвращать этот код при верном entity (см. выше).
Если запрос вообще не удалось разобрать, следует возвращать 418.
415 Unsupported Media Type
Возвращается при загрузке файлов на сервер, если фактический формат переданного файла не поддерживается. Также может возвращаться, если не удалось распарсить JSON запроса, или сам запрос пришёл не в формате JSON.
418 I’m a Teapot
Возвращается для неизвестных серверу запросов, которые не удалось даже разобрать. Обычно это указывает на ошибку в клиенте, типа ошибки при формировании URI, либо что версии протокола клиента и сервера не совпадают.
419 Authentication Timeout
Отправляется, если клиенту нужно пройти повторную авторизацию (например, протухли куки или CSRF токены). При этом на клиенте могут быть несохранённые данные, которые будут потеряны, если просто выкинуть клиента на страницу авторизации.
422 Unprocessable Entity
Запрос корректно разобран, но содержание запроса не прошло серверную валидацию.
Например, в теле запроса были указаны неизвестные серверу поля, или не были указаны обязательные, или с содержимым полей что-то не так.
Обычно это означает ошибку в введённых пользователем данных, но может также быть вызвано ошибкой на клиенте или несовпадением версий.
500 Internal Server Error
Возвращается, если на сервере вылетело необработанное исключение или произошла другая необработанная ошибка времени исполнения.
Всё, что может сделать клиент в этом случае — это уведомить пользователя и сделать console.error(err) для более продвинутых товарищей (админов, разработчиков и тестировщиков).
501 Not Implemented
Возвращается, если текущий метод неприменим (не реализован) к объекту запроса.
Ну вот, в общем-то, и всё. Спасибо за внимание!


