Python, библиотека Requests: быстрый старт

04.03.2018

Категория: Web-разработка

Готовы начать? Эта статья дает достаточное представление о том, как начать работу с Requests. Давайте начнем с нескольких простых примеров.

Создание запроса

Создание запроса с помощью Requests — это очень просто. Начните с импорта модуля:

>>> import requests

Теперь попробуем получить веб-страницу. Например, давайте получим публичный тайм-лайн GitHub.

>>> r = requests.get('https://api.github.com/events')

Теперь у нас есть объект Response с именем r. Мы можем получить всю необходимую информацию из этого объекта.

Простой API Requests означает, что все формы HTTP запросов являются очевидными. Например, вот как вы можете сделать HTTP POST запрос:

>>> r = requests.post("http://httpbin.org/post")

Круто? А как насчет других типов HTTP запроса: PUT, DELETE, HEAD и OPTIONS? Их выполнить так же просто:

>>> r = requests.put("http://httpbin.org/put")
>>> r = requests.delete("http://httpbin.org/delete")
>>> r = requests.head("http://httpbin.org/get")
>>> r = requests.options("http://httpbin.org/get")

Это уже хорошо. Даже здорово. Но это далеко не все из того, что может делать Requests.

Передача параметров в URL

Часто вы хотите послать какие-то данные в строке запроса URL. Если вы строите URL вручную, то эти данные будут представлены в нем в виде пар ключ-значение после знака вопроса. Например, httpbin.org/get?key=val. Requests позволяет передать эти аргументы в качестве словаря, используя аргумент params. В качестве примера, если вы хотите передать key1=value1 и key2=value2 ресурсу httpbin.org/get, вы должны использовать следующий код:

>>> payload = {'key1': 'value1', 'key2': 'value2'}
>>> r = requests.get("http://httpbin.org/get", params=payload)

Вы можете видеть, что URL был закодирован правильно:

>>> print(r.url)
http://httpbin.org/get?key2=value2&key1=value1

Заметим, что любой ключ словаря, значение которого None, не будет добавлен к строке запроса URL.

Содержимое ответа

Мы можем читать содержимое ответа сервера. Рассмотрим тайм-лайн GitHub снова:

>>> import requests
>>> r = requests.get('https://api.github.com/events') 
>>> r.text 
u'[{"repository":{"open_issues":0,"url":"https://github.com/...

Requests будет автоматически декодировать содержимое ответа сервера. Большинство Unicode кодировок без проблем декодируются.

Когда вы делаете запрос, Requests делает предположение о кодировке, основанное на заголовках HTTP. Кодировка текста, угаданная Requests, используется при обращение к r.text. Вы можете узнать, какую кодировку использует Requests, и изменить её воспользовавшись свойством r.encoding:

>>> r.encoding
'utf-8' 
>>> r.encoding = 'ISO-8859-1' 

Если вы измените кодировку, Requests будет использовать новое значение r.encoding всякий раз, когда вы будете использовать r.text. Вы можете сделать это в любой ситуации, где нужна более специализированная логика работы с кодировкой содержимого ответа. Например, в HTML и XML есть возможность задавать кодировку прямо в теле документа. В подобных ситуациях вы должны использовать r.content, чтобы найти кодировку, а затем установить r.encoding. Это позволит вам использовать r.text с правильной кодировкой.

Requests может также использовать пользовательские кодировки в случае, если вы в них нуждаетесь. Если вы создали свою собственную кодировку и зарегистрировали её в модуле codecs, вы можете просто использовать название кодека в качестве значения r.encoding, и Requests будет работать с этой кодировкой для вас.

Бинарное содержимое ответа

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

>>> r.content 
b'[{"repository":{"open_issues":0,"url":"https://github.com/...

Передача со сжатием gzip и deflate автоматически декодируется.

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

>>> from PIL import Image 
>>> from StringIO import StringIO 
>>> i = Image.open(StringIO(r.content))

JSON содержимое ответа

С библиотекой Requests также поставляется встроенный JSON декодер на случай, если вы имеете дело с данными в формате JSON:

>>> import requests 
>>> r = requests.get('https://api.github.com/events') 
>>> r.json() 
[{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...

В случае, если декодирование JSON не удается, r.json выбрасывает исключение. Например, если приходит ответ с кодом статуса 401 (неавторизованный), попытка обращения к r.json выбрасывает исключение ValueError: No JSON object could be decoded.

Необработанное содержимое ответа

В редких случаях, когда вы хотите получить доступ к сырому ответу сервера на уровне сокета, вы можете обратиться к r.raw. Если вы хотите сделать это, убедитесь, что вы установили stream=True в вашем первом запросе. После этого вы уже можете проделать следующее:

>>> r = requests.get('https://api.github.com/events', stream=True) 
>>> r.raw 
<requests.packages.urllib3.response.HTTPResponse object at 0x101194810> 
>>> r.raw.read(10) 
'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'

Теперь вы можете использовать подобный код как шаблон, чтобы сохранить получаемый поток в файл:

>>> with open(filename, 'wb') as fd:
>>>     for chunk in r.iter_content(chunk_size):
>>>         fd.write(chunk)

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

Пользовательские HTTP заголовки

Если вы хотите добавить HTTP-заголовки в запрос, просто передайте соответствующий словарь в параметре headers. Например, мы не указали заголовок content-type в предыдущем примере. Давайте сделаем это сейчас:

>>> import json
>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'} 
>>> headers = {'content-type': 'application/json'}
>>> r = requests.post(url, data=json.dumps(payload), headers=headers)

Более сложные POST запросы

Зачастую вы хотите послать некоторые form-encoded данные также как это делается в HTML форме. Чтобы сделать это, просто передайте соответствующий словарь в аргументе data. Ваш словарь данных в таком случае будет автоматически закодирован как HTML форма, когда будет сделан запрос:

>>> payload = {'key1': 'value1', 'key2': 'value2'}
>>> r = requests.post("http://httpbin.org/post", data=payload)
>>> print(r.text)
{ ... "form": { "key2": "value2", "key1": "value1" }, ... }

Но есть много случаев, когда вы можете захотеть отправить данные, которые не закодированы методом form-encoded. Если вы передадите в запрос строку вместо словаря, то данные будут отправлены в неизменном виде. Например, API v3 GitHub принимает JSON-закодированные POST/PATCH данные:

>>> import json url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}
>>> r = requests.post(url, data=json.dumps(payload))

Как послать Multipart-Encoded файл

Requests позволяет легко послать на сервер Multipart-Encoded файлы:

>>> url = 'http://httpbin.org/post' 
>>> files = {'file': open('report.xls', 'rb')}
>>> r = requests.post(url, files=files) 
>>> r.text 
{ ... "files": { "file": "<censored...binary...data>" }, ... }

Вы можете установить имя файла, content-type и заголовки в явном виде:

>>> url = 'http://httpbin.org/post' 
>>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}
>>> r = requests.post(url, files=files)
>>> r.text
{ ... "files": { "file": "<censored...binary...data>" }, ... }

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

>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')}
>>> r = requests.post(url, files=files)
>>> r.text
{ ... "files": { "file": "some,data,to,send\nanother,row,to,send\n" }, ... }

В случае, если вы отправляете очень большой файл как multipart/form-data, вы можете захотеть отправить запрос потоком. По умолчанию requests не поддерживает этого, но есть отдельный пакет, который это делает — requests-toolbelt.

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

Коды состояния ответа

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

>>> r = requests.get('http://httpbin.org/get')
>>> r.status_code
200

Для удобства Requests также поставляется со встроенным объектом подстановок кодов состояния:

>>> r.status_code == requests.codes.ok
True

Если мы сделали плохой запрос (ошибка 4XX клиента или ошибка 5XX ответа сервера), то мы можем возбудить исключение с помощью Response.raise_for_status():

>>> bad_r = requests.get('http://httpbin.org/status/404') 
>>> bad_r.status_code 
404
>>> bad_r.raise_for_status() 
Traceback (most recent call last):
  File "requests/models.py", line 832, in raise_for_status
    raise http_error
requests.exceptions.HTTPError: 404 Client Error
Traceback (most recent call last):
  File "requests/models.py", line 832, in raise_for_status
    raise http_error
requests.exceptions.HTTPError: 404 Client Error

Но если status_code для r оказался 200, то когда мы вызываем raise_for_status() мы получаем:

>>> r.raise_for_status()
None

Это значит, что все в порядке.

Заголовки ответов

Мы можем просматривать заголовки ответа сервера, используя словарь Python:

>>> r.headers { 'content-encoding': 'gzip', 'transfer-encoding': 'chunked', 'connection': 'close', 'server': 'nginx/1.0.4', 'x-runtime': '148ms', 'etag': '"e1ca502697e5c9317743dc078f67693f"', 'content-type': 'application/json' }

Однако это словарь особого рода: он сделан специально для HTTP заголовков. Согласно RFC 7230, имена заголовков HTTP нечувствительны к регистру.

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

>>> r.headers['[red]Content-Type[/red]'] 
'application/json'
>>> r.headers.get('[red]content-type[/red]')
'application/json'

Cookies

Если ответ содержит cookie, вы можете быстро получить к ним доступ:

>>> url = 'http://example.com/some/cookie/setting/url'
>>> r = requests.get(url)
>>> r.cookies['example_cookie_name']
'example_cookie_value'

Для отправки собственных cookie на сервер, вы можете использовать параметр cookies:

>>> url = 'http://httpbin.org/cookies'
>>> cookies = dict(cookies_are='working')
>>> r = requests.get(url, cookies=cookies)
>>> r.text
'{"cookies": {"cookies_are": "working"}}'

Редиректы и история

По умолчанию Requests будет выполнять редиректы для всех HTTP методов, кроме HEAD. Мы можем использовать свойство history объекта Response, чтобы отслеживать редиректы. Список Response.history содержит объекты Response, которые были созданы во время выполнения запроса. Список сортируется от более ранних к более поздним ответам.

Например, GitHub перенаправляет все HTTP запросы на HTTPS:

>>> r = requests.get('http://github.com') 
>>> r.url 'https://github.com/' 
>>> r.status_code 
200 
>>> r.history 
[<Response [301]>]

Если вы используете GET, OPTIONS, POST, PUT, PATCH или DELETE, вы можете отключить обработку редиректов с помощью параметра allow_redirects:

>>> r = requests.get('http://github.com', allow_redirects=False) 
>>> r.status_code 
301 
>>> r.history 
[]

Если вы используете HEAD, вы можете включить обработку редиректов:

>>> r = requests.head('http://github.com', allow_redirects=True) 
>>> r.url 'https://github.com/' 
>>> r.history 
[<Response [301]>]

Тайм-ауты

Вы можете сказать Requests прекратить ожидание ответа после определенного количества секунд с помощью параметра timeout:

>>> requests.get('http://github.com', timeout=0.001) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)

Примечание: timeout это не ограничение по времени полной загрузки ответа. Исключение возникает, если сервер не дал ответ за timeout секунд (точнее, если ни одного байта не было получено от основного сокета за timeout секунд).

Ошибки и исключения

  • В случае неполадок в сети (например, отказа DNS, отказался соединения и т.д.), Requests возбудит исключение ConnectionError.
  • В более редком случае неверного HTTP ответа, Requests возбудит исключение HTTPError.
  • Если превышено время ожидания ответа, возбуждается исключение Timeout.
  • Если запрос превышает заданное значение максимального количества редиректов, то возбуждается исключение TooManyRedirects.

Все исключения, которые возбуждает непосредственно Requests, унаследованы от requests.exceptions.RequestException.

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.