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
.