Requests: Быстрый старт

Если вы хотите быстро приступить к работе, эта страница отлично вводит в основы использования Requests.

Для начала убедитесь, что:

Приступим к простым примерам.

Отправка запроса

Отправка запроса с помощью Requests предельно проста. Начните с импорта модуля:

>>> import requests

Попробуем получить веб-страницу. В этом примере мы запрашиваем публичную ленту событий GitHub:

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

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

Благодаря интуитивно понятному API все виды HTTP-запросов выглядят предельно просто. Например, выполнить HTTP POST запрос можно так:

>>> r = requests.post('https://httpbin.org/post', data={'key': 'value'})

Звучит удобно, не так ли? А что насчёт других HTTP-методов, таких как PUT, DELETE, HEAD и OPTIONS? Они работают аналогичным образом:

>>> r = requests.put('https://httpbin.org/put', data={'key': 'value'})
>>> r = requests.delete('https://httpbin.org/delete')
>>> r = requests.head('https://httpbin.org/get')
>>> r = requests.options('https://httpbin.org/get')

Это лишь малая часть возможностей Requests.

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

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

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

Вы можете убедиться, что URL сформирован правильно, если выведете его на печать:

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

Учтите, что если значением какого-либо ключа является None, этот параметр не будет включён в строку запроса.

Также возможно передавать список значений для одного ключа:

>>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']}
>>> r = requests.get('https://httpbin.org/get', params=payload)
>>> print(r.url)
https://httpbin.org/get?key1=value1&key2=value2&key2=value3

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

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

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

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

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

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

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

Также Requests поддерживает пользовательские кодировки: если вы зарегистрировали свой кодек через модуль codecs, достаточно указать его имя в r.encoding, и Requests выполнит декодирование.

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

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

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

Содержимое, сжатое с помощью gzip или deflate, автоматически декодируется.

Сжатие по схеме br также декодируется, если установлена библиотека Brotli (например, brotli или brotlicffi).

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

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

Работа с JSON-ответами

Requests имеет встроенный декодер JSON для удобной работы с JSON-данными:

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

Если декодирование не удаётся, вызов метода r.json() породит исключение. Например, если сервер возвращает 204 (No Content) или содержит некорректный JSON, попытка вызвать r.json() приведёт к исключению requests.exceptions.JSONDecodeError. Это обёрточное исключение позволяет унифицировать обработку ошибок, возникающих в разных версиях Python и JSON-библиотеках.

Важно понимать, что успешное выполнение r.json() не гарантирует, что запрос завершился успешно. Некоторые серверы возвращают JSON даже при ошибках (например, HTTP 500). Такой JSON всё равно будет декодирован. Чтобы проверить успешность запроса, вызовите r.raise_for_status() или проверьте, что r.status_code соответствует ожидаемому значению.

Необработанный ответ

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

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

Как правило, для сохранения потока данных в файл рекомендуется использовать следующий шаблон:

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

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

Важно понимать разницу между Response.iter_content и Response.raw. Метод iter_content автоматически декодирует сжатые данные (например, через gzip и deflate), в то время как Response.raw возвращает сырые байты без преобразования. Если вам необходим доступ к исходным данным, используйте Response.raw.

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

Если необходимо добавить HTTP-заголовки к запросу, просто передайте соответствующий словарь в параметр headers.

Например, в предыдущем примере мы не указывали user-agent:

>>> url = 'https://api.github.com/some/endpoint'
>>> headers = {'user-agent': 'my-app/0.0.1'}
>>> r = requests.get(url, headers=headers)

Примечание: Пользовательские заголовки имеют меньший приоритет по сравнению с данными, полученными из других источников. Например:

  • Заголовки авторизации, заданные через параметр headers=, будут переопределены, если учётные данные указаны в файле .netrc, а те, в свою очередь, заменятся параметром auth=. Requests ищет файл netrc в ~/.netrc, ~/_netrc или по пути, заданному переменной окружения NETRC.
  • Заголовки авторизации удаляются при перенаправлении на другой хост.
  • Заголовки Proxy-Authorization переопределяются учётными данными, указанными в URL.
  • Заголовок Content-Length устанавливается автоматически, если Requests может определить размер содержимого.

Кроме того, Requests не изменяет своё поведение в зависимости от установленных пользовательских заголовков — они просто передаются в итоговый запрос.

Примечание: Все значения заголовков должны быть строками, байт-строками или юникодом. Несмотря на допустимость юникода, рекомендуется избегать его использования в значениях заголовков.

Расширенные POST-запросы

Чаще всего требуется отправлять данные в формате, аналогичном HTML-форме. Для этого достаточно передать словарь в параметр data — он будет автоматически преобразован в формат form-encoded:

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

Параметр data также поддерживает передачу нескольких значений для одного ключа. Это можно сделать, передав данные в виде списка кортежей или словаря, где значениями являются списки. Такой подход особенно полезен, если форма содержит несколько полей с одинаковым именем:

>>> payload_tuples = [('key1', 'value1'), ('key1', 'value2')]
>>> r1 = requests.post('https://httpbin.org/post', data=payload_tuples)
>>> payload_dict = {'key1': ['value1', 'value2']}
>>> r2 = requests.post('https://httpbin.org/post', data=payload_dict)
>>> print(r1.text)
{
  ...
  "form": {
    "key1": [
      "value1",
      "value2"
    ]
  },
  ...
}
>>> r1.text == r2.text
True

Иногда может потребоваться отправить данные без их кодирования в формат формы. Если передать строку вместо словаря, данные будут отправлены напрямую.

Например, API GitHub v3 принимает данные POST/PATCH в формате JSON:

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

Обратите внимание, что приведённый код НЕ добавляет заголовок Content-Type (то есть, он НЕ устанавливает его в application/json).

Если необходимо автоматически установить заголовок и не хочется самостоятельно сериализовать словарь, можно передать данные через параметр json (добавленный в версии 2.4.2) — они будут закодированы автоматически:

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

Учтите, что параметр json игнорируется, если переданы параметры data или files.

Отправка файлов с использованием multipart

Requests значительно упрощает загрузку файлов, отправляемых с кодированием multipart:

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

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

>>> url = 'https://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 = 'https://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. Рекомендуется ознакомиться с его документацией requests-toolbelt для получения подробной информации.

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

Рекомендуется открывать файлы в бинарном режиме (binary mode <tut-files>), так как Requests может автоматически установить заголовок Content-Length, значение которого будет равно количеству байт в файле. Ошибки могут возникнуть, если файл открыт в текстовом режиме.

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

Мы можем проверить код состояния ответа следующим образом:

>>> r = requests.get('https://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('https://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

А так как для запроса r код состояния равен 200, вызов

>>> r.raise_for_status()
None

подтверждает, что ошибок нет.

HTTP-заголовки ответа

Мы можем просмотреть заголовки ответа сервера, используя обычный словарь 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, имена заголовков не чувствительны к регистру.

Таким образом, обращаться к заголовкам можно, используя любой регистр:

>>> r.headers['Content-Type']
'application/json'

>>> r.headers.get('content-type')
'application/json'

Также сервер может отправлять один и тот же заголовок несколько раз, но Requests объединяет их в один элемент словаря, как предписано RFC 7230:

Получатель может объединить несколько полей с одинаковым именем в одну строку "имя: значение", соединяя значения через запятую, не меняя смысла сообщения.

Cookies

Если в ответе присутствуют cookies, к ним можно быстро обратиться:

>>> url = 'http://example.com/some/cookie/setting/url'
>>> r = requests.get(url)

>>> r.cookies['example_cookie_name']
'example_cookie_value'

Чтобы отправить свои cookies на сервер, воспользуйтесь параметром cookies:

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

Cookies возвращаются в объекте ~requests.cookies.RequestsCookieJar, который действует как словарь, но предоставляет расширенный интерфейс для работы с несколькими доменами или путями. Такие cookie-jar'ы также можно передавать в запросах:

>>> jar = requests.cookies.RequestsCookieJar()
>>> jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies')
>>> jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere')
>>> url = 'https://httpbin.org/cookies'
>>> r = requests.get(url, cookies=jar)
>>> r.text
'{"cookies": {"tasty_cookie": "yum"}}'

Перенаправления и история запросов

По умолчанию Requests автоматически обрабатывает перенаправления для всех методов, кроме HEAD.

Для отслеживания перенаправлений можно воспользоваться свойством history объекта Response. Этот список содержит объекты requests.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]>]

Таймауты

Вы можете задать ограничение на время ожидания ответа, используя параметр timeout. Практически в любом проекте рекомендуется указывать этот параметр для всех запросов, чтобы избежать бесконечного ожидания:

>>> requests.get('https://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)
Параметр timeout не ограничивает общее время загрузки ответа. Исключение возникает, если сервер не отправляет данные в течение заданного времени (то есть, если ни один байт не получен от сокета за указанное число секунд). Если таймаут не указан, Requests может ждать ответа бесконечно.

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

При возникновении сетевых проблем (например, сбой DNS или отказ в соединении) Requests возбуждает исключение requests.exceptions.ConnectionError.

Метод Response.raise_for_status() вызывает исключение requests.exceptions.HTTPError, если HTTP-запрос завершился с ошибкой. Если время ожидания истекло, возникает исключение requests.exceptions.Timeout. При превышении допустимого числа перенаправлений возбуждается исключение requests.exceptions.TooManyRedirects.

Все исключения, генерируемые Requests, наследуются от requests.exceptions.RequestException.


Готовы к большим возможностям? Ознакомьтесь с разделом Requests: Продвинутое использование.