Работа с REST API на Python
Introduction | |
Подготовка | |
GET | |
GET с параметрами | |
POST | |
PUT с токеном | |
Обработка ответа сервера | |
json.dumps(): вывести читаемый json | |
Вытащить часть ответа | |
Аутентификация | |
Задержка |
Introduction
В этой статье вы узнаете как писать запросы к REST API на Python 3.
Если ваша цель - создание своего REST API - переходите к статье
«Flask»
Прежде чем что-то устанавливать убедитесь, что вы знакомы с работой в
виртуальном окружении
Python.
Прочитать об этом можно в статье
«Виртуальные окружения в Python»
Подготовка
Активируйте ваше виртуальное окружение и установите requests командой
python3 -m pip install requests
Изучите список установленных модулей
python3 -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
To сделать GET запрос достаточно импортировать
requests
и выполнить
requests.get
Создайте файл
rdemo.py
следующего содержания:
import requests
r = requests.get('https:/xkcd.com/353/')
print(r)
Запустите скрипт командой
python3 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}")
python3 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
To отправить и проверить POST запрос внесите небольшие изменения в код.
Очевидно, что GET нужно заменить на POST, также params нужно заменить на data
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'])
python3 rdemo.py
{'established': '2018', 'website': 'heihei.ru'}
PUT
Всё аналогично POST просто замените post на put
Рассмотрим пример, в котором нужно передать в теле запроса json, а также использовать имеющийся токен для авторизации
import requests
import json
url = "http://eth1.ru"
new_access_token = sdlfjsljglkjfd;lkgjdlkhjlkjgdlkhjlkdjglkj
body = """[{"id":"1234abc","name":"andrei","website":"eth1.ru"}]"""
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()
Обработка ответа
Рассмотрим приёмы, которые пригодятся при работе с полученными данными
Предположим, получены данные в формате
json.
Нужно извлечь из них токен, который хранится в access_token
To его получить, воспользуйтесь методом json() который возвращает словарь (<class 'dict'>).
И из этого словаря получите токен по ключевому слову access_token
r_dict = r.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)
python3 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)
Задержка равна одной секунде. А ждать ответ можно до трёх секунд.
python3 timeout_demo.py
<Response [200]>
Измените код так, чтобы ответ приходил заведомо позже чем наш таймаут в три секунды.
import requests
r = requests.get('https://httpbin.org/delay/7', timeout=3)
print(r)
Задержка равна семи секундам. А ждать ответ можно по-прежнему только до трёх секунд.
python3 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)
except (requests.exceptions.Timeout,
requests.exceptions.ConnectionError
) as err:
print('Response is taking too long.')
else:
print(r)
python3 timeout_demo.py
Response is taking too long.