Работа с REST API на Python
Введение
В этой статье вы узнаете как выполнять запросы к REST API на Python 3 и обрабатывать ответы.
Если ваша цель - создание своего REST API - переходите к статье
«Flask»
Прежде чем что-то устанавливать убедитесь, что вы знакомы с работой в
виртуальном окружении
Python.
Прочитать об этом можно в статье
«Виртуальные окружения в Python»
Подготовка
Активируйте ваше виртуальное окружение и установите requests командой
python -m pip install requests
Изучите список установленных модулей
python -m pip list
Package Version ---------- --------- certifi 2020.6.20 chardet 3.0.4 idna 2.10 pip 20.2.3 requests 2.24.0 setuptools 50.3.1 urllib3 1.25.10 wheel 0.35.1
requests подтягивает за собой requests, certifi , chardet, idna, urllib3
Проверить куда установился requests в этом окружении можно командой
python3 -m pip show requests
Name: requests Version: 2.24.0 Summary: Python HTTP for Humans. Home-page: https://requests.readthedocs.io Author: Kenneth Reitz Author-email: me@kennethreitz.org License: Apache 2.0 Location: /home/andrei/python/virtualenvs/answerit_env/lib/python3.8/site-packages Requires: certifi, chardet, urllib3, idna Required-by:
GET
Чтобы сделать GET запрос достаточно импортировать
requests
и выполнить
requests.get
Создайте файл
rdemo.py
следующего содержания:
import requests
r = requests.get('https://xkcd.com/353/')
print(r)
Запустите скрипт командой
python rdemo.py
<Response [200]>
Если получили 200 значит всё хорошо. Изменим наш код, чтобы узнать, какие действия мы можем
произвести с объектом <Response [200]>
dir(r) выдаст список доступных атрибутов и методов
import requests
r = requests.get('https://topbicycle.ru/b/stels_pilot_950_md_26.php')
print(dir(r))
['__attrs__', '__bool__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__nonzero__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_content', '_content_consumed', '_next', 'apparent_encoding', 'close', 'connection', 'content', 'cookies', 'elapsed', 'encoding', 'headers', 'history', 'is_permanent_redirect', 'is_redirect', 'iter_content', 'iter_lines', 'json', 'links', 'next', 'ok', 'raise_for_status', 'raw', 'reason', 'request', 'status_code', 'text', 'url']
Более подробную информацию можно получить заменив dir(r) на help(r)
Если вам интересно - прочитайте статью
requests help
Из этого документа можно узнать http статус содержится в атрибуте status_code
import requests
r = requests.get('https://topbicycle.ru/b/stels_pilot_950_md_26.php')
print(r.status_code)
Если всё прошло успешно, то получите
200
ok вернёт True если ответ не 4XX или 5XX
headers возвращает заголовок ответа
Также из этого документа можно узнать что text возвращает содержимое ответа в формате unicode а
content содержимое ответа в байтах
Имените код на
import requests
r = requests.get('https://xkcd.com/353/')
print("content:")
print("-----")
print(r.content)
print("-----")
print("text:")
print("-----")
print(r.text)
И изучите разницу
Обычно content используют для работы с изображениями
Перейдите на
TopBicycle.ru
и найдите первое фото велосипеда.
Скопируйте его url
https://topbicycle.ru/b/img/stels_pilot_950_MD_26.jpg
Теперь измените код так, чтобы сохранить изображение в файл
import requests
r = requests.get("https://topbicycle.ru/b/img/stels_pilot_950_MD_26.jpg")
with open("bike.jpg", "wb") as f:
f.write(r.content)
Если всё прошло успешно, в вашей папке появится следующее фото
GET с параметрами
Для проверки ваших навыков работы с REST API можно воспользоваться сайтом
httpbin.org
Он будет возвращать вам обратно ваш запрос.
Изменим код
rdemo.py
:
Параметры можно записать сразу после url за знаком вопроса, но надёжнее
оформить их в виде отдельного словаря (dictionary).
import requests
payload = {'page': 2, 'count': 25}
r = requests.get("https://httpbin.org/get", params=payload)
print(f"url: {r.url} \n\ntext: \n {r.text}")
python rdemo.py
url: https://httpbin.org/get?page=2&count=25 text: { "args": { "count": "25", "page": "2" }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Host": "httpbin.org", "User-Agent": "python-requests/2.24.0", "X-Amzn-Trace-Id": "Root=1-5f8c2d50-4f48f16c0991ad0e6e45676e" }, "origin": "87.92.8.47", "url": "https://httpbin.org/get?page=2&count=25" }
POST
Чтобы отправить и проверить POST запрос внесите небольшие изменения в код.
Очевидно, что GET нужно заменить на POST, также params нужно заменить на data или json
data – (optional) Dictionary, list of tuples, bytes, or file-like object to send in the body of the Request.
json – (optional) A JSON serializable Python object to send in the body of the Request.
import requests
payload = {'website': 'heihei.ru', 'established': 2018}
r = requests.post('https://httpbin.org/post', data=payload)
print(f"url: {r.url} \n\ntext: \n {r.text}")
url: https://httpbin.org/post text: { "args": {}, "data": "", "files": {}, "form": { "established": "2018", "website": "heihei.ru" }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Content-Length": "34", "Content-Type": "application/x-www-form-urlencoded", "Host": "httpbin.org", "User-Agent": "python-requests/2.24.0", "X-Amzn-Trace-Id": "Root=1-5f8c30ac-6b32899f073f9df4055c55c4" }, "json": null, "origin": "87.92.8.47", "url": "https://httpbin.org/post" }
Ответы, которые мы получили с httpbin приходили в формате json.
Для работы c json бывает удобно использовать встроенный метод .json()
В этом случае данные записываются в словарь и к ним очень легко обращаться.
Обратите внимание на "form" данные, которые были переданы возвращаются в этом поле.
Изменим код так, чтобы на экран выводились только эти данные
import requests
payload = {'website': 'heihei.ru', 'established': 2018}
r = requests.post('https://httpbin.org/post', data=payload)
r_dict = r.json()
print(r_dict['form'])
python rdemo.py
{'established': '2018', 'website': 'heihei.ru'}
PUT
Всё аналогично POST просто замените post на put
Рассмотрим пример, в котором нужно передать в теле запроса json, а также использовать имеющийся токен для авторизации
import requests
import json
url = "http://urn.su"
new_access_token = "sdlfjsljglkjfd;lkgjdlkhjlkjgdlkhjlkdjglkj"
body = """[{"id":"1234abc","name":"andrei","website":"urn.su"}]"""
payload = json.loads(body)
json_headers = {
"Content-type": "application/json",
"Authorization": "Bearer %s" %new_access_token
}
r = requests.put(url, headers=json_headers, json=payload, verify=False)
Обратите внимание на следующие моменты
- При передаче JSON нужно обязательно указать заголовок 'Content-type': 'application/json'
- Токен передаётся с помощью Bearer
- Можно легко преобразовать данные в JSON с помощью json.loads()
Обработка ответа
Рассмотрим приёмы, которые пригодятся при работе с полученными данными
Простейший ответ сервера, например 200 может вообще не содержать никаких данных
response = requests.delete(url, headers=headers, verify=False) print(f"type(response): {type(response)}") print(f"response.__repr__: {repr(response)}") print(f"response.__str__: {str(response)}") print(f"response.text: {response.text}")
type(response): <class 'requests.models.Response'> response.__repr__: <Response [200]> response.__str__: <Response [200]> response.text:
Очень часто данные приходят в формате
json.
Например, нужно извлечь из них токен, который хранится в access_token
Чтобы его получить, воспользуйтесь методом json()
который возвращает
словарь
(<class 'dict'>).
И из этого словаря получите токен по ключевому слову access_token
r_dict = response.json()
access_token = r_dict["access_token"]
json.dumps
Если вы хотите сразу же изучить полученный json можно воспользоваться методом dumps()
import json
print(json.dumps(response.json(), indent=4))
Если вы получили не json а dict, json.dumps нужно использовать так:
import json
print(json.dumps(resp.dict, indent=4, sort_keys=True))
sort_keys делать не обязательно. Это я для примера показываю, что можно отсортировтаь ключевые слова.
Вытащить часть ответа
Часто интерес бывает не весь ответ, а только часть. О том как её грамотно выделить из ответа читайте в статье
«Как обратиться ко вложенным в json объектам»
Аутентификация
Рассмотрим базовую
аутентификацию
на сайте httpbin
Придумайте любое имя пользоватлея и пароль к нему.
Я придумал andrey с паролем heihei
Перейдите на
httpbin.org
. Убедитесь, что в адресной строке стоит basic-auth/andrey/heihei
либо те логин и пароль, что придумали вы.
Введите ваши логин и пароль
Убедитесь, что аутентификация прошла успешно
Теперь проделаем такую же аутентификацию с помощью Python
Создайте файл
auth_demo.py
со следующим кодом
import requests
r = requests.get(
'https://httpbin.org/basic-auth/andrey/heihei',
auth=('andrey', 'heihei')
)
print(r.text)
python auth_demo.py
{ "authenticated": true, "user": "andrey" }
Ответ совпадает с тем что мы уже получали в браузере
Выполните такой же запрос, но с неправильным паролем. Убедитесь в том, что text ничего не содержит. Замените
print(r.text) на print(r) и убедитесь, что полученный объект это
<Response [401]>
Задержка
Часто бывает нужно ограничить время ожидания ответа. Это можно сделать с помощью параметра timeout
Перейдите на
httpbin.org
раздел - / #/ Dynamic_data / delete_delay__delay_
и изучите документацию - если делать запрос на этот url можно выставлять время, через которое
будет отправлен ответ.
Создайте файл
timeout_demo.py
следующего содержания
import requests
r = requests.get('https://httpbin.org/delay/1', timeout=3)
print(r)
Задержка равна одной секунде. А ждать ответ можно до трёх секунд.
python timeout_demo.py
<Response [200]>
Измените код так, чтобы ответ приходил заведомо позже чем наш таймаут в три секунды.
import requests
r = requests.get('https://httpbin.org/delay/7', timeout=3)
print(r)
Задержка равна семи секундам. А ждать ответ можно по-прежнему только до трёх секунд.
python timeout_demo.py
Traceback (most recent call last): File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 421, in _make_request six.raise_from(e, None) File "<string>", line 3, in raise_from File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 416, in _make_request httplib_response = conn.getresponse() File "/usr/lib/python3.8/http/client.py", line 1347, in getresponse response.begin() File "/usr/lib/python3.8/http/client.py", line 307, in begin version, status, reason = self._read_status() File "/usr/lib/python3.8/http/client.py", line 268, in _read_status line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1") File "/usr/lib/python3.8/socket.py", line 669, in readinto return self._sock.recv_into(b) File "/usr/lib/python3/dist-packages/urllib3/contrib/pyopenssl.py", line 326, in recv_into raise timeout("The read operation timed out") socket.timeout: The read operation timed out During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/usr/lib/python3/dist-packages/requests/adapters.py", line 439, in send resp = conn.urlopen( File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 719, in urlopen retries = retries.increment( File "/usr/lib/python3/dist-packages/urllib3/util/retry.py", line 400, in increment raise six.reraise(type(error), error, _stacktrace) File "/usr/lib/python3/dist-packages/six.py", line 703, in reraise raise value File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 665, in urlopen httplib_response = self._make_request( File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 423, in _make_request self._raise_timeout(err=e, url=url, timeout_value=read_timeout) File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 330, in _raise_timeout raise ReadTimeoutError( urllib3.exceptions.ReadTimeoutError: HTTPSConnectionPool(host='httpbin.org', port=443): Read timed out. (read timeout=3) During handling of the above exception, another exception occurred: Traceback (most recent call last): File "timeout_demo.py", line 4, in <module> r = requests.get('https://httpbin.org/delay/7', timeout=3) File "/usr/lib/python3/dist-packages/requests/api.py", line 75, in get return request('get', url, params=params, **kwargs) File "/usr/lib/python3/dist-packages/requests/api.py", line 60, in request return session.request(method=method, url=url, **kwargs) File "/usr/lib/python3/dist-packages/requests/sessions.py", line 533, in request resp = self.send(prep, **send_kwargs) File "/usr/lib/python3/dist-packages/requests/sessions.py", line 646, in send r = adapter.send(request, **kwargs) File "/usr/lib/python3/dist-packages/requests/adapters.py", line 529, in send raise ReadTimeout(e, request=request) requests.exceptions.ReadTimeout: HTTPSConnectionPool(host='httpbin.org', port=443): Read timed out. (read timeout=3)
Если хотите обработать исключение - измените код используя try except
import requests try: r = requests.get('https://httpbin.org/delay/7', timeout=3) print(r) except ( requests.exceptions.Timeout, requests.exceptions.ConnectionError ) as err: print('Response is taking too long.', err)
python timeout_demo.py
Response is taking too long. HTTPSConnectionPool(host='httpbin.org', port=443): Read timed out. (read timeout=3)
Работа с сертификатами
В Python по умолчанию используются не сертификаты системы, а сертификаты из модуля certif
Проверить где они расположены можно следуюущим образом
python >>> import certifi >>> certifi.where()
'/home/andrei/python/venv/lib/python3.8/site-packages/certifi/cacert.pem'