.. module:: requests.models
Eager to get started? This page gives a good introduction in how to get started
with Requests.
First, make sure that:
- Requests is :ref:`installed <install>`
- Requests is :ref:`up-to-date <updates>`
Let’s get started with some simple examples.
Make a Request
Making a request with Requests is very simple.
Begin by importing the Requests module:
>>> import requests
Now, let’s try to get a webpage. For this example, let’s get GitHub’s public
timeline:
>>> r = requests.get('https://api.github.com/events')
Now, we have a :class:`Response <requests.Response>` object called r. We can
get all the information we need from this object.
Requests’ simple API means that all forms of HTTP request are as obvious. For
example, this is how you make an HTTP POST request:
>>> r = requests.post('https://httpbin.org/post', data={'key': 'value'})
Nice, right? What about the other HTTP request types: PUT, DELETE, HEAD and
OPTIONS? These are all just as simple:
>>> 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')
That’s all well and good, but it’s also only the start of what Requests can
do.
Passing Parameters In URLs
You often want to send some sort of data in the URL’s query string. If
you were constructing the URL by hand, this data would be given as key/value
pairs in the URL after a question mark, e.g. httpbin.org/get?key=val.
Requests allows you to provide these arguments as a dictionary of strings,
using the params keyword argument. As an example, if you wanted to pass
key1=value1 and key2=value2 to httpbin.org/get, you would use the
following code:
>>> payload = {'key1': 'value1', 'key2': 'value2'}
>>> r = requests.get('https://httpbin.org/get', params=payload)
You can see that the URL has been correctly encoded by printing the URL:
>>> print(r.url) https://httpbin.org/get?key2=value2&key1=value1
Note that any dictionary key whose value is None will not be added to the
URL’s query string.
You can also pass a list of items as a value:
>>> 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
Response Content
We can read the content of the server’s response. Consider the GitHub timeline
again:
>>> import requests
>>> r = requests.get('https://api.github.com/events')
>>> r.text
'[{"repository":{"open_issues":0,"url":"https://github.com/...
Requests will automatically decode content from the server. Most unicode
charsets are seamlessly decoded.
When you make a request, Requests makes educated guesses about the encoding of
the response based on the HTTP headers. The text encoding guessed by Requests
is used when you access r.text. You can find out what encoding Requests is
using, and change it, using the r.encoding property:
>>> r.encoding 'utf-8' >>> r.encoding = 'ISO-8859-1'
If you change the encoding, Requests will use the new value of r.encoding
whenever you call r.text. You might want to do this in any situation where
you can apply special logic to work out what the encoding of the content will
be. For example, HTML and XML have the ability to specify their encoding in
their body. In situations like this, you should use r.content to find the
encoding, and then set r.encoding. This will let you use r.text with
the correct encoding.
Requests will also use custom encodings in the event that you need them. If
you have created your own encoding and registered it with the codecs
module, you can simply use the codec name as the value of r.encoding and
Requests will handle the decoding for you.
Binary Response Content
You can also access the response body as bytes, for non-text requests:
>>> r.content
b'[{"repository":{"open_issues":0,"url":"https://github.com/...
The gzip and deflate transfer-encodings are automatically decoded for you.
The br transfer-encoding is automatically decoded for you if a Brotli library
like brotli or brotlicffi is installed.
For example, to create an image from binary data returned by a request, you can
use the following code:
>>> from PIL import Image >>> from io import BytesIO >>> i = Image.open(BytesIO(r.content))
JSON Response Content
There’s also a builtin JSON decoder, in case you’re dealing with JSON data:
>>> import requests
>>> r = requests.get('https://api.github.com/events')
>>> r.json()
[{'repository': {'open_issues': 0, 'url': 'https://github.com/...
In case the JSON decoding fails, r.json() raises an exception. For example, if
the response gets a 204 (No Content), or if the response contains invalid JSON,
attempting r.json() raises requests.exceptions.JSONDecodeError. This wrapper exception
provides interoperability for multiple exceptions that may be thrown by different
python versions and json serialization libraries.
It should be noted that the success of the call to r.json() does not
indicate the success of the response. Some servers may return a JSON object in a
failed response (e.g. error details with HTTP 500). Such JSON will be decoded
and returned. To check that a request is successful, use
r.raise_for_status() or check r.status_code is what you expect.
Raw Response Content
In the rare case that you’d like to get the raw socket response from the
server, you can access r.raw. If you want to do this, make sure you set
stream=True in your initial request. Once you do, you can do this:
>>> r = requests.get('https://api.github.com/events', stream=True)
>>> r.raw
<urllib3.response.HTTPResponse object at 0x101194810>
>>> r.raw.read(10)
'x1fx8bx08x00x00x00x00x00x00x03'
In general, however, you should use a pattern like this to save what is being
streamed to a file:
with open(filename, 'wb') as fd:
for chunk in r.iter_content(chunk_size=128):
fd.write(chunk)
Using Response.iter_content will handle a lot of what you would otherwise
have to handle when using Response.raw directly. When streaming a
download, the above is the preferred and recommended way to retrieve the
content. Note that chunk_size can be freely adjusted to a number that
may better fit your use cases.
Note
An important note about using Response.iter_content versus Response.raw.
Response.iter_content will automatically decode the gzip and deflate
transfer-encodings. Response.raw is a raw stream of bytes — it does not
transform the response content. If you really need access to the bytes as they
were returned, use Response.raw.
Custom Headers
If you’d like to add HTTP headers to a request, simply pass in a dict to the
headers parameter.
For example, we didn’t specify our user-agent in the previous example:
>>> url = 'https://api.github.com/some/endpoint'
>>> headers = {'user-agent': 'my-app/0.0.1'}
>>> r = requests.get(url, headers=headers)
Note: Custom headers are given less precedence than more specific sources of information. For instance:
- Authorization headers set with headers= will be overridden if credentials
are specified in.netrc, which in turn will be overridden by theauth=
parameter. Requests will search for the netrc file at ~/.netrc, ~/_netrc,
or at the path specified by the NETRC environment variable. - Authorization headers will be removed if you get redirected off-host.
- Proxy-Authorization headers will be overridden by proxy credentials provided in the URL.
- Content-Length headers will be overridden when we can determine the length of the content.
Furthermore, Requests does not change its behavior at all based on which custom headers are specified. The headers are simply passed on into the final request.
Note: All header values must be a string, bytestring, or unicode. While permitted, it’s advised to avoid passing unicode header values.
More complicated POST requests
Typically, you want to send some form-encoded data — much like an HTML form.
To do this, simply pass a dictionary to the data argument. Your
dictionary of data will automatically be form-encoded when the request is made:
>>> payload = {'key1': 'value1', 'key2': 'value2'}
>>> r = requests.post("https://httpbin.org/post", data=payload)
>>> print(r.text)
{
...
"form": {
"key2": "value2",
"key1": "value1"
},
...
}
The data argument can also have multiple values for each key. This can be
done by making data either a list of tuples or a dictionary with lists
as values. This is particularly useful when the form has multiple elements that
use the same key:
>>> 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
There are times that you may want to send data that is not form-encoded. If
you pass in a string instead of a dict, that data will be posted directly.
For example, the GitHub API v3 accepts JSON-Encoded POST/PATCH data:
>>> import json
>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}
>>> r = requests.post(url, data=json.dumps(payload))
Please note that the above code will NOT add the Content-Type header
(so in particular it will NOT set it to application/json).
If you need that header set and you don’t want to encode the dict yourself,
you can also pass it directly using the json parameter (added in version 2.4.2)
and it will be encoded automatically:
>>> url = 'https://api.github.com/some/endpoint' >>> payload = {'some': 'data'}>>> r = requests.post(url, json=payload)
Note, the json parameter is ignored if either data or files is passed.
POST a Multipart-Encoded File
Requests makes it simple to upload Multipart-encoded files:
>>> url = 'https://httpbin.org/post'
>>> files = {'file': open('report.xls', 'rb')}
>>> r = requests.post(url, files=files)
>>> r.text
{
...
"files": {
"file": "<censored...binary...data>"
},
...
}
You can set the filename, content_type and headers explicitly:
>>> 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>"
},
...
}
If you want, you can send strings to be received as files:
>>> url = 'https://httpbin.org/post'
>>> files = {'file': ('report.csv', 'some,data,to,sendnanother,row,to,sendn')}
>>> r = requests.post(url, files=files)
>>> r.text
{
...
"files": {
"file": "some,data,to,send\nanother,row,to,send\n"
},
...
}
In the event you are posting a very large file as a multipart/form-data
request, you may want to stream the request. By default, requests does not
support this, but there is a separate package which does —
requests-toolbelt. You should read the toolbelt’s documentation for more details about how to use it.
For sending multiple files in one request refer to the :ref:`advanced <advanced>`
section.
Warning
It is strongly recommended that you open files in :ref:`binary
mode <tut-files>`. This is because Requests may attempt to provide
the Content-Length header for you, and if it does this value
will be set to the number of bytes in the file. Errors may occur
if you open the file in text mode.
Response Status Codes
We can check the response status code:
>>> r = requests.get('https://httpbin.org/get')
>>> r.status_code
200
Requests also comes with a built-in status code lookup object for easy
reference:
>>> r.status_code == requests.codes.ok True
If we made a bad request (a 4XX client error or 5XX server error response), we
can raise it with
:meth:`Response.raise_for_status() <requests.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
But, since our status_code for r was 200, when we call
raise_for_status() we get:
>>> r.raise_for_status() None
All is well.
Response Headers
We can view the server’s response headers using a Python dictionary:
>>> r.headers
{
'content-encoding': 'gzip',
'transfer-encoding': 'chunked',
'connection': 'close',
'server': 'nginx/1.0.4',
'x-runtime': '148ms',
'etag': '"e1ca502697e5c9317743dc078f67693f"',
'content-type': 'application/json'
}
The dictionary is special, though: it’s made just for HTTP headers. According to
RFC 7230, HTTP Header names
are case-insensitive.
So, we can access the headers using any capitalization we want:
>>> r.headers['Content-Type']
'application/json'
>>> r.headers.get('content-type')
'application/json'
It is also special in that the server could have sent the same header multiple
times with different values, but requests combines them so they can be
represented in the dictionary within a single mapping, as per
RFC 7230:
A recipient MAY combine multiple header fields with the same field name
into one «field-name: field-value» pair, without changing the semantics
of the message, by appending each subsequent field value to the combined
field value in order, separated by a comma.
Cookies
If a response contains some Cookies, you can quickly access them:
>>> url = 'http://example.com/some/cookie/setting/url' >>> r = requests.get(url) >>> r.cookies['example_cookie_name'] 'example_cookie_value'
To send your own cookies to the server, you can use the cookies
parameter:
>>> url = 'https://httpbin.org/cookies'
>>> cookies = dict(cookies_are='working')
>>> r = requests.get(url, cookies=cookies)
>>> r.text
'{"cookies": {"cookies_are": "working"}}'
Cookies are returned in a :class:`~requests.cookies.RequestsCookieJar`,
which acts like a dict but also offers a more complete interface,
suitable for use over multiple domains or paths. Cookie jars can
also be passed in to requests:
>>> 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"}}'
Redirection and History
By default Requests will perform location redirection for all verbs except
HEAD.
We can use the history property of the Response object to track redirection.
The :attr:`Response.history <requests.Response.history>` list contains the
:class:`Response <requests.Response>` objects that were created in order to
complete the request. The list is sorted from the oldest to the most recent
response.
For example, GitHub redirects all HTTP requests to HTTPS:
>>> r = requests.get('http://github.com/')
>>> r.url
'https://github.com/'
>>> r.status_code
200
>>> r.history
[<Response [301]>]
If you’re using GET, OPTIONS, POST, PUT, PATCH or DELETE, you can disable
redirection handling with the allow_redirects parameter:
>>> r = requests.get('http://github.com/', allow_redirects=False)
>>> r.status_code
301
>>> r.history
[]
If you’re using HEAD, you can enable redirection as well:
>>> r = requests.head('http://github.com/', allow_redirects=True)
>>> r.url
'https://github.com/'
>>> r.history
[<Response [301]>]
Timeouts
You can tell Requests to stop waiting for a response after a given number of
seconds with the timeout parameter. Nearly all production code should use
this parameter in nearly all requests. Failure to do so can cause your program
to hang indefinitely:
>>> 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)
Note
timeout is not a time limit on the entire response download;
rather, an exception is raised if the server has not issued a
response for timeout seconds (more precisely, if no bytes have been
received on the underlying socket for timeout seconds). If no timeout is specified explicitly, requests do
not time out.
Errors and Exceptions
In the event of a network problem (e.g. DNS failure, refused connection, etc),
Requests will raise a :exc:`~requests.exceptions.ConnectionError` exception.
:meth:`Response.raise_for_status() <requests.Response.raise_for_status>` will
raise an :exc:`~requests.exceptions.HTTPError` if the HTTP request
returned an unsuccessful status code.
If a request times out, a :exc:`~requests.exceptions.Timeout` exception is
raised.
If a request exceeds the configured number of maximum redirections, a
:exc:`~requests.exceptions.TooManyRedirects` exception is raised.
All exceptions that Requests explicitly raises inherit from
:exc:`requests.exceptions.RequestException`.
Ready for more? Check out the :ref:`advanced <advanced>` section.
If you’re on the job market, consider taking this programming quiz. A substantial donation will be made to this project, if you find a job through this platform.
Requests — это модуль для языка Python, который используют для упрощения работы с HTTP-запросами. Он удобнее и проще встроенного Urllib настолько, что даже в документации Python рекомендовано использовать Requests.
Установка библиотеки Requests
Самый простой вариант установки сторонних пакетов в Python — использовать pip — систему управления пакетами. Обычно pip предустанавливается вместе с интерпретатором. Если его нет — можно скачать. Для этого нужно ввести в командную строку:
Linux / MacOS
python -m ensurepip --upgrade
Windows
py -m ensurepip --upgrade
Когда pip установлен, для установки модуля Requests нужно ввести команду:
pip install requests
Как настроить библиотеку Requests. Библиотека не требует дополнительной настройки — ею можно сразу же пользоваться.
Начало работы. Давайте рассмотрим пример простейшего запроса в модуле Requests:
import requests # делаем запрос на чтение страницы https://sky.pro/media/
response = requests.get('https://sky.pro/media/')
print(response.ok) # проверяем успешен ли запрос?
print(response.text) # выводим полученный ответ на экран
А вот как сделать то же самое, но при помощи встроенной библиотеки Urllib:
from urllib.request import urlopen
# открываем запрос на чтение страницы http://sky.pro/media
with urlopen('http://sky.pro/media') as response:
response_status = response.status # сохраняем статус запроса в переменную
html = response.read() # вычитываем ответ в переменную
print(response_status == 200) # проверяем успешен ли запрос
print(html.decode()) # выводим полученный ответ на экран
Модуль Requests в Python упрощает и автоматизирует многие действия, которые в стандартной библиотеке надо делать самостоятельно. Именно за это её любят и используют многие разработчики.
Давайте разберёмся, как работать с Requests, и из чего состоят HTTP-запросы.
Методы HTTP-запросов
HTTP — это протокол передачи информации в интернете. Он описывает правила и формат общения между двумя сторонами. Например, как браузеру описать запрос, а серверу — сформировать ответ. HTTP — это текстовый протокол, поэтому его может прочитать и человек.
Давайте разберем простейший запрос:
GET /media/ HTTP/1.1 Host: sky.pro
Первая строка формирует запрос: мы говорим серверу, что хотим прочитать (GET) ресурс по адресу /media/. В конце указывается версия протокола: HTTP/1.1.
Начиная со второй строки передается дополнительная информация, которая называется заголовками. Она опциональная — кроме заголовка Host. Он указывает домен, на котором находится запрашиваемый ресурс.
HTTP-ответ выглядит аналогично:
HTTP/1.1 200 OK Content-Type: text/html <тело ответа>
В первой строке указывается версия протокола и код ответа — статус, который описывает результат запроса. В следующих строках, так же, как и в запросе, перечисляются заголовки. В данном случае сервер говорит, что в ответе находится HTML-страница (Content-Type: text/html).
И в самом конце находится тело ответа: файл, HTML-страница или ничего. Браузер отрисовывает тело ответа — это уже то, что видит человек, когда загружает страницу.
Методы HTTP-запросов нужны, чтобы объяснить серверу, какое действие мы хотим совершить над ресурсом. Ресурс — это цель HTTP-запроса. Это может быть документ, фотография или просто веб-страница.
Разберем на примерах распространённые методы — в чём их суть и чем они отличаются. Важно: ниже разбираются механизмы работы каждого метода в том виде, в котором они описаны в спецификации. На практике поведение может отличаться, но такое встречается нечасто.
OPTIONS
Метод OPTIONS нужен, чтобы спросить сервер о том, какие методы поддерживает ресурс. Он редко используется напрямую, обычно вызывается браузером автоматически. Поддерживается не всеми сайтами/ресурсами. Пример:
HTTP-ответ выглядит аналогично:
import requests
response = requests.options('https://httpbin.org')
print(response.text) # будет пустым
print(response.headers['Allow']) # 'HEAD, GET, OPTIONS'
GET
GET — самый распространённый HTTP-метод. Его используют для чтения интернет-ресурса. Браузер отправляет метод GET, когда мы открываем какой-либо сайт. Пример:
import requests
response = requests.get('https://httpbin.org/get')
print(response.text)
POST
Метод POST используют для отправки на сервер данных, которые передаются в теле запроса. Для этого при вызове requests.post() надо указать аргумент data, который принимает на вход словарь, список кортежей, байты или файл.
Если для передачи данных используется формат JSON, вместо data можно указать json. Это просто удобная конвенция, которая правильно формирует отправляемый запрос. Пример:
import requests
data_response = requests.post('https://httpbin.org/post', data={'foo': 'bar'})
print(data_response.text) # переданные данные находятся по ключу form
json_response = requests.post('https://httpbin.org/post', json={'foo': 'bar'})
print(data_response.text) # ключ form пустой, теперь данные лежат в json
HEAD
Этот метод очень похож на GET — с той лишь разницей, что HEAD возвращает пустое тело ответа. Он нужен, когда нужно посмотреть только на заголовки, не загружая ответ целиком.
Например, мы хотим иметь свежую версию PDF-файла с расписанием автобусов. Файл хранится на каком-то сайте и периодически обновляется. Вместо того, чтобы каждый раз скачивать и сверять файл вручную, можно использовать метод HEAD. Он поможет быстро проверить дату изменения файла по заголовкам ответа.
import requests
response = requests.get('https://httpbin.org/head')
print(response.text) # ответ будет пустым
print(response.headers)
PUT
Метод PUT очень похож на POST — с той разницей, что несколько последовательных вызовов PUT должны приводить к одному и тому же результату.
POST этого не гарантирует и может привести к неожиданным результатам, например к дублированию созданной сущности.
import requests
response = requests.put('https://httpbin.org/put', data={'foo': 'bar'})
print(response.text)
PATCH
PATCH аналогичен методу POST, но с двумя отличиями: он используется для частичных изменений ресурса и его нельзя использовать в HTML-формах.
В теле запроса передается набор модификаций, которые надо применить.
import requests
response = requests.patch('https://httpbin.org/patch', data={'foo': 'bar'})
print(response.text)
DELETE
Метод используется для удаления ресурса. Поддерживает передачу данных, однако не требует её: тело запроса может быть пустым.
Как и PUT, последовательный вызов DELETE должен приводить к одному и тому же результату.
import requests
response = requests.delete('https://httpbin.org/delete')
print(response.text)
HTTP-коды состояний
Каждый ответ HTTP-запроса обязательно имеет код состояния — трехзначное число, которое как-то характеризует полученный результат. По этому коду можно понять, всё ли успешно отработало, и если произошла ошибка, то почему.
Всего выделяют пять групп кодов состояний:
1хх-коды.
К этой группе относятся информационные коды состояний. Они сообщают клиенту о промежуточном статусе запроса и не являются финальным результатом.
Их немного, и останавливаться на них мы не будем, потому что они встречаются нечасто.
2хх-коды.
Коды из этой группы означают, что запрос принят и обработан сервером без ошибок:
- 200 OK — запрос выполнен успешно. Чаще всего встречается именно это число.
- 201 Created — в результате запроса был создан новый ресурс. Как правило, этим кодом отвечают на POST- и иногда PUT-запросы.
- 202 Accepted — запрос принят, но ещё не выполнен. Используется, когда по какой-то причине сервер не может выполнить его сразу. Например, если обработку делает какой-то сторонний процесс, который выполняется раз в день.
- 204 No Content — указывает, что тело ответа пустое, но заголовки могут содержать полезную информацию. Не используется с методом HEAD, поскольку ответ на него всегда должен быть пустым.
3хх-коды.
Это группа кодов перенаправления. Это значит, что клиенту нужно сделать какое-то действие, чтобы запрос продолжил выполняться:
- 301 Moved Permanently — URL запрашиваемого ресурса изменился, новый URL содержится в ответе.
- 302 Found — аналогичен предыдущему коду. Отличие в том, что URL изменился временно. При этом статусе состояния поисковые системы не будут менять ссылку в своей поисковой выдаче на новую.
- 304 Not Modified — означает, что содержимое ресурса было закешировано, его содержимое не поменялось и запрос можно не продолжать.
4хх-коды.
Это коды ошибок, которые допустил клиент при формировании запроса:
- 400 Bad Request — запрос сформирован с ошибкой, поэтому сервер не может его обработать. Причин может быть много, но чаще всего ошибку надо искать в теле запроса.
- 401 Unauthorized — для продолжения необходимо залогиниться.
- 403 Forbidden — пользователь залогинен, но у него нет прав для доступа к ресурсу.
- 404 Not Found — всем известный код: страница не найдена. Некоторые сайты могут возвращать 404 вместо 403, чтобы скрыть информацию от неавторизованных пользователей.
- 405 Method Not Allowed — данный ресурс не поддерживает метод запроса. Например, так бывает, если разработчик хочет отправить PUT-запрос на ресурс, который его не поддерживает.
- 429 Too Many Requests — означает, что сработал защитный механизм: он ограничивает слишком частые запросы от одного пользователя. Таким образом защищаются от DDoS- или brute-force-атак.
5хх-коды.
Это ошибки, которые возникли на сервере во время выполнения запроса:
- 500 Internal Server Error — на сервере произошла неожиданная ошибка. Как правило, происходит из-за того, что в коде сервера возникает исключение.
- 502 Bad Gateway — возникает, если на сервере используется обратный прокси, который не смог достучаться до приложения.
- 503 Service Unavailable — сервер пока не готов обработать запрос. В ответе также может содержаться информация о том, когда сервис станет доступен.
- 504 Gateway Timeout — эта ошибка означает, что обратный прокси не смог получить ответ за отведенное время (обычно — 60 секунд).
Заголовки, текст ответа и файлы Cookie
Теперь рассмотрим, как работать с запросами и ответами в Requests. Чтобы увидеть результат HTTP-запроса, можно использовать один из трех способов.
Выбор способа зависит от того, какие данные мы получили. В непонятной ситуации можно использовать атрибут text, который возвращает содержимое в виде строки:
import requests
response = requests.get('https://httpbin.org/get')
print(response.text)
Если заранее известно, что ответ будет в формате JSON, можно использовать одноименный атрибут, который автоматически распарсит ответ и вернет его в виде словаря:
json_response = response.json() print(json_response)
Обратите внимание, как изменится вывод функции print().
Наконец, если ответом на запрос является файл, стоит использовать атрибут content, который возвращает байты:
import requests
response = requests.get('https://httpbin.org/image/jpeg')
print(response.content)
Попробуйте вывести на экран response.text для предыдущего запроса и сравните результат.
Заголовок — это дополнительная информация, которой обмениваются клиент и сервер. В заголовках могут содержаться: размер ответа (Content-Length), формат передаваемых данных (Content-Type) или информация о клиенте (User-Agent).
Полный список очень длинный, знать их все необязательно, а часть и вовсе подставляется автоматом. Например, модуль Requests зачастую сам проставляет Content-Type — формат передаваемых данных.
Заголовок состоит из названия и значения, которые разделяются двоеточием, поэтому удобнее всего передавать их в виде словаря. Рассмотрим на примере, как это работает:
import requests
response = requests.get('https://httpbin.org/image', headers={'Accept': 'image/jpeg'})
print(response.headers)
Здесь мы передали заголовок, который указывает, в каком формате мы хотим получить изображение. Попробуйте поменять значение на image/png и посмотрите, как изменится ответ.
Так же можно посмотреть и на заголовки запроса:
print(response.request.headers)
Обратите внимание, что Requests сам подставил информацию о клиенте — User-Agent.
Cookie (куки) — это информация, которую сервер отправляет браузеру для хранения. Они позволяют зафиксировать некоторое состояние. Например, в куки может храниться информация о том, что пользователь уже залогинен. Она хранится в браузере и передается на сервер при каждом запросе, поэтому нам не нужно каждый раз проходить авторизацию заново.
Работать с куками в модуле Requests очень просто:
import requests
response = requests.get('https://httpbin.org/cookies', cookies={'foo': 'bar'})
print(response.text)
Посмотреть, какие куки пришли от сервера, можно при помощи атрибута cookies объекта Response:
print(response.cookies)
Как отправлять запросы при помощи Python Requests
Рассмотрим несколько частых примеров использования модуля Requests, чтобы понять, как отправлять запросы.
Скачивание файлов
import requests
response = requests.get('https://www.python.org/static/img/python-logo.png')
with open('python_logo.png', 'wb') as image:
image.write(response.content)
Выше описан не самый эффективный способ скачивания файлов. Если файл окажется большого размера, код выше загрузит результат целиком в оперативную память. В лучшем случае программа упадет с ошибкой, в худшем — всё намертво зависнет.
Вот как это можно исправить:
import requests
response = requests.get('https://www.python.org/static/img/python-logo@2x.png', stream=True)
with open('python_logo.png', 'wb') as image:
for chunk in response.iter_content(chunk_size=1024):
image.write(chunk)
В этом варианте мы используем параметр stream=True, который открывает соединение, но не скачивает содержимое. Затем мы задаем размер чанка — кусочка информации, который будет скачиваться за одну итерацию цикла, и делаем его равным 1 Кб (1024 байт). Модуль Requests сам закрывает соединение после прочтения последнего чанка.
Чтобы заранее узнать размер файла, можно воспользоваться методом HEAD. Эта информация передается в заголовке ‘Content-Length’ и исчисляется в байтах.
import requests
head_response = requests.head('https://www.python.org/static/img/python-logo@2x.png')
image_size = int(head_response.headers['Content-Length'])
print('Размер загружаемого файла: {0} кб'.format(image_size / 1024))
Авторизация на сайте
Рассмотрим два способа авторизации, которые встречаются чаще всего: Basic Auth и Bearer Auth. В обоих случаях механизм очень похожий — запрос должен передать заголовок ‘Authorization’ с каким-то значением. Для Basic Auth — это логин и пароль, закодированные в base64, для Bearer — токен, который мы получили на сайте заранее.
Для базовой авторизации у модуля Requests есть очень удобный параметр auth=, который делает всю работу за нас:
import requests
response = requests.get('https://httpbin.org/basic-auth/foo/bar')
print(response.status_code) # 401
response = requests.get('https://httpbin.org/basic-auth/foo/bar', auth=('foo', 'bar'))
print(response.status_code) # 200
print(response.request.headers[‘Authorization’]) # 'Basic Zm9vOmJhcg=='
Обратите внимание, что модуль Requests сам добавил заголовок Authorization и подставил туда закодированные логин и пароль.
Для Bearer Auth нам придется добавлять его самостоятельно:
import requests
response = requests.get('https://httpbin.org/bearer')
print(response.status_code) # 401
headers = {'Authorization': 'Bearer some_token'}
response = requests.get('https://httpbin.org/bearer', headers=headers)
print(response.status_code) # 200
У каждого API своя спецификация — вместо Bearer может быть Token или что-то другое. Поэтому важно внимательно читать документацию сервиса.
Мультискачивание
Напишем код, который умеет скачивать сразу несколько файлов. Для этого вынесем работу с модулем Requests в отдельную функцию и параметризируем место сохранения файла.
Не забывайте про сохранение файла по чанкам, чтобы крупные файлы не загружались в память целиком.
import requests
def download_file(url, save_path):
response = requests.get(url, stream=True)
with open(save_path, 'wb') as file:
for chunk in response.iter_content(chunk_size=1024):
file.write(chunk)
download_list = [
'https://cdn.pixabay.com/photo/2022/04/10/19/33/house-7124141_1280.jpg',
'https://cdn.pixabay.com/photo/2022/08/05/18/50/houseplant-7367379_1280.jpg',
'https://cdn.pixabay.com/photo/2022/06/09/04/53/ride-7251713_1280.png',
]
for url in download_list:
save_path = url.split('/')[-1]
download_file(url, save_path)
Заключение
Модуль Requests — мощный инструмент, с которым разработчик может сделать сложный HTTP-запрос всего в пару строк. У него интуитивно понятный интерфейс, поэтому он так популярен в сообществе Python.
С помощью модуля реквест можно выполнить множество функций: от авторизации на сайте до скачивания нескольких файлов одновременно.
Содержание
- Введение в тему
- Создание get и post запроса
- Передача параметров в url
- Содержимое ответа response
- Бинарное содержимое ответа
- Содержимое ответа в json
- Необработанное содержимое ответа
- Пользовательские заголовки
- Более сложные post запросы
- Post отправка multipart encoded файла
- Коды состояния ответа
- Заголовки ответов
- Cookies
- Редиректы и история
- Тайм ауты
- Ошибки и исключения
Введение в тему
Модуль python requests – это общепринятый стандарт для работы с запросами по протоколу HTTP.
Этот модуль избавляет Вас от необходимости работать с низкоуровневыми деталями. Работа с запросами становится простой и элегантной.
В этом уроке будут рассмотрены самые полезные функций библиотеки requests и различные способы их использования.
Перед использованием модуля его необходимо установить:
Создание get и post запроса
Сперва необходимо добавить модуль Requests в Ваш код:
Создадим запрос и получим ответ, содержащий страницу и все необходимые данные о ней.
import requests
response = requests.get('https://www.google.ru/')
В переменную response попадает ответ на запрос. Благодаря этому объекту можно использовать любую информацию, относящуюся к этому ответу.
Сделать POST запрос так же очень просто:
import requests
response = requests.post('https://www.google.ru/', data = {'foo':3})
Другие виды HTTP запросов, к примеру: PUT, DELETE, и прочих, выполнить ничуть не сложнее:
import requests
response = requests.put('https://www.google.ru/', data = {'foo':3})
response = requests.delete('https://www.google.ru/')
response = requests.head('https://www.google.ru/')
response = requests.options('https://www.google.ru/')
Передача параметров в url
Иногда может быть необходимо отправить различные данные вместе с запросом URL. При ручной настройке URL, параметры выглядят как пары ключ=значение после знака «?». Например, https://www.google.ru/search?q=Python. Модуль Requests предоставляет возможность передать эти параметры как словарь, применяя аргумент params. Если вы хотите передать q = Python и foo=’bar’ ресурсу google.ru/search, вы должны использовать следующий код:
import requests
params_dict = {'q':'Python', 'foo':'bar'}
response = requests.get('https://www.google.ru/search', params=params_dict)
print(response.url)
#Вывод:
https://www.google.ru/search?q=Python&foo=bar
Здесь мы видим, что URL был сформирован именно так, как это было задумано.
Пара ключ=значение, где значение равняется None, не будет добавлена к параметрам запроса URL.
Так же есть возможность передавать в запрос список параметров:
import requests
params_dict = {'q':'Python', 'foo':['bar', 'eggs']}
response = requests.get('https://www.google.ru/search', params=params_dict)
print(response.url)
#Вывод:
https://www.google.ru/search?q=Python&foo=bar&foo=eggs
Содержимое ответа response
Код из предыдущего листинга создаёт объект Response, содержащий ответ сервера на наш запрос. Обратившись к его атрибуту .url можно просмотреть адрес, куда был направлен запрос. Атрибут .text позволяет просмотреть содержимое ответа. Вот как это работает:
import requests
params_dict = {'q':'Python'}
response = requests.get('https://www.google.ru/search', params=params_dict)
print(response.text)
#Вывод:<!doctype html><html lang="ru"><head><meta charset="UTF-8"><meta content="/images/branding/googleg/1x/googleg_standard_color_128dp.png"…
Библиотека автоматически пытается определить кодировку ответа основываясь на его заголовках. Узнать, какую кодировку выбрал модуль, можно следующим образом:
import requests
params_dict = {'q':'Python'}
response = requests.get('https://www.google.ru/search', params=params_dict)
print(response.encoding)
#Вывод:
windows-1251
Можно так же самостоятельно установить кодировку используя атрибут .encoding.
import requests
params_dict = {'q':'Python'}
response = requests.get('https://www.google.ru/search', params=params_dict)
response.encoding = 'utf-8' # указываем необходимую кодировку вручную
print(response.encoding)
#Вывод:
utf-8
Бинарное содержимое ответа
Существует возможность просмотра ответа в виде байтов:
import requests
params_dict = {'q':'Python'}
response = requests.get('https://www.google.ru/search', params=params_dict)
print(response.content)
#Вывод:
b'<!doctype html><html lang="ru"><head><meta charset="UTF-8"><meta content="/images/branding/googleg/1x/googleg_standard_color_128dp.png" …
При передаче со сжатием ответ автоматически декодируется для Вас.
Содержимое ответа в json
Так же в Requests есть встроенная обработка ответов в формате JSON:
import requests
import json
response = requests.get(‘http://api.open-notify.org/astros.json’)
print(json.dumps(response.json(), sort_keys=True, indent=4))
#Вывод:
{
«message»: «success»,
«number»: 10,
«people»: [
{
«craft»: «ISS»,
«name»: «Mark Vande Hei»
},
{
«craft»: «ISS»,
«name»: «Oleg Novitskiy»
},
…
[/dm_code_snippet]
Если ответ не является JSON, то .json выбросит исключение:
import requests
import json
response = requests.get('https://www.google.ru/search')
print(json.dumps(response.json(), sort_keys=True, indent=4))
#Вывод:
…
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
Необработанное содержимое ответа
Если Вам нужно получить доступ к ответу сервера в чистом виде на уровне сокета, обратитесь к атрибуту .raw. Для этого необходимо указать параметр stream=True в запросе. Этот параметр заставляет модуль читать данные по мере их прибытия.
import requests
response = requests.get('https://www.google.ru/', stream=True)
print(response.raw)
print('Q'*10)
print(response.raw.read(15))
#Вывод:
<urllib3.response.HTTPResponse object at 0x000001E368771FA0>
QQQQQQQQQQ
b'x1fx8bx08x00x00x00x00x00x02xffxc5[[sxdb'
Так же можно использовать метод .iter_content. Этот метод итерирует данные потокового ответа и это позволяет избежать чтения содержимого сразу в память для больших ответов. Параметр chunk_size – это количество байтов, которые он должен прочитать в памяти. Параметр chunk_size можно произвольно менять.
import requests
response = requests.get('https://www.google.ru/', stream=True)
print(response.iter_content)
print('Q'*10)
print([i for i in response.iter_content(chunk_size=256)])
#Вывод:
<bound method Response.iter_content of <Response [200]>>
QQQQQQQQQQ
[b'<!doctype html><html itemscope="" itemtype="http://sche', b'ma.org/WebPage" lang="ru"><head><meta content=…
response.iter_content будет автоматически декодировать сжатый ответ. Response.raw — чистый набор байтов, неизменённое содержимое ответа.
Пользовательские заголовки
Если необходимо установить заголовки в HTTP запросе, передайте словарь с ними в параметр headers. Значения заголовка должны быть типа string, bytestring или unicode. Имена заголовков не чувствительны к регистру символов.
В следующем примере мы устанавливаем информацию об используемом браузере:
import requests
response = requests.get('https://www.google.ru/', headers={'user-agent': 'unknown_browser'})
print(response.request.headers)
# Вывод:
{'user-agent': 'unknown_browser', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
Более сложные post запросы
Существует способ отправить данные так, будто это результат заполнения формы на сайте:
import requests
response = requests.post('https://httpbin.org/post', data={'foo': 'bar'})
print(response.text)
# Вывод:
{
"args": {},
"data": "",
"files": {},
"form": {
"foo": "bar"
},
"headers": {
…
Параметр data может иметь произвольное количество значений для каждого ключа. Для этого необходимо указать data в формате кортежа, либо в виде dict со списками значений.
import requests
response = requests.post('https://httpbin.org/post', data={'foo':['bar', 'eggs']})
print(response.json()['form'])
print('|'*10)
response = requests.post('https://httpbin.org/post', data=[('foo', 'bar'), ('foo', 'eggs')])
print(response.json()['form'])
# Вывод:
{'foo': ['bar', 'eggs']}
||||||||||
{'foo': ['bar', 'eggs']}
Если нужно отправить данные, не закодированные как данные формы, то передайте в запрос строку вместо словаря. Тогда данные отправятся в изначальном виде.
import requests
response = requests.post('https://httpbin.org/post', data={'foo': 'bar'})
print('URL:', response.request.url)
print('Body:', response.request.body)
print('-' * 10)
response = requests.post('https://httpbin.org/post', data='foo=bar')
print('URL:', response.request.url)
print('Body:', response.request.body)
# Вывод:
URL: https://httpbin.org/post
URL: https://httpbin.org/post
Body: foo=bar
----------
URL: https://httpbin.org/post
Body: foo=bar
Post отправка multipart encoded файла
Запросы упрощают загрузку файлов с многостраничным кодированием (Multipart-Encoded):
import requests
url = 'https://httpbin.org/post'
files = {'file': open('report.xls', 'rb')}
response = requests.post(url, files=files)
print(response.text)
# Вывод:
{
...
"files": {
"file": "<censored...binary...data>"
},
...
}
Вы можете установить имя файла, content_type и заголовки в явном виде:
import requests
url = 'https://httpbin.org/post'
files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}
response = requests.post(url, files=files)
print(response.text)
# Вывод:
{
...
"files": {
"file": "<censored...binary...data>"
},
...
}
Можете отправить строки, которые будут приняты в виде файлов:
import requests
url = 'https://httpbin.org/post'
files = {'file': ('report.csv', 'some,data,to,sendnanother,row,to,sendn')}
response = requests.post(url, files=files)
print(response.text)
# Вывод:
{
...
"files": {
"file": "some,data,to,send\nanother,row,to,send\n"
},
...
}
Коды состояния ответа
Возможно, наиболее важные данные (первые – уж точно), которые вы можете получить, используя библиотеку requests, является код состояния ответа.
Так, 200 статус означает, что запрос выполнен успешно, тогда как 404 статус означает, что ресурс не найден.
Важнее всего то, с какой цифры начинается код состояния:
- 1XX — информация
- 2XX — успешно
- 3XX — перенаправление
- 4XX — ошибка клиента (ошибка на нашей стороне)
- 5XX — ошибка сервера (самые страшные коды для разработчика)
Используя атрибут .status_code можно получить статус, который вернул сервер:
import requests
response = requests.get('https://www.google.ru/')
print(response.status_code)
# Вывод:
200
.status_code вернул 200 — это означает, что запрос успешно выполнен и сервер вернул запрашиваемые данные.
При желании, такую информацию можно применить в Вашем Пайтон скрипте для принятия решений:
import requests
response = requests.get('https://www.google.ru/')
if response.status_code == 200: print('Успех!')elif response.status_code == 404: print('Страница куда-то пропала…')
# Вывод:
Успех!
Если код состояния response равен 200, то скрипт выведет «Успех!», но, если он равен 404, то скрипт вернёт «Страница куда-то пропала…».
Если применить модуль Response в условном выражении и проверить логическое значение его экземпляра (if response) то он продемонстрирует значение True, если код ответа находится в диапазоне между 200 и 400, и False во всех остальных случаях.
Упростим код из предыдущего примера:
import requests
response = requests.get('https://www.google.ru/fake/')
if response:
print('Успех!')
else:
print('Хьюстон, у нас проблемы!')
# Вывод:
Хьюстон, у нас проблемы!
Данный способ не проверяет, что код состояния равен именно 200.
Причиной этого является то, что response с кодом в диапазоне от 200 до 400, такие как 204 и 304, тоже являются успешными, ведь они возвращают обрабатываемый ответ. Следовательно, этот подход делит все запросы на успешные и неуспешные – не более того. Во многих случаях Вам потребуется более детальная обработка кодов состояния запроса.
Вы можете вызвать exception, если requests.get был неудачным. Такую конструкцию можно создать вызвав .raise_for_status() используя конструкцию try- except:
import requests
from requests.exceptions import HTTPError
for url in ['https://www.google.ru/', 'https://www.google.ru/invalid']:
try:
response = requests.get(url)
response.raise_for_status()
except HTTPError:
print(f'Возникла ошибка HTTP: {HTTPError}')
except Exception as err:
print(f'Возникла непредвиденная ошибка: {err}')
else:
print('Успех!')
# Вывод:
Успех!
Возникла ошибка HTTP: <class 'requests.exceptions.HTTPError'>
Заголовки ответов
Мы можем просматривать заголовки ответа сервера:
import requests
response = requests.get('https://www.google.ru/')
print(response.headers)
# Вывод:
{'Date': 'Sun, 27 Jun 2021 13:43:17 GMT', 'Expires': '-1', 'Cache-Control': 'private, max-age=0', 'Content-Type': 'text/html; charset=windows-1251', 'P3P': 'CP="This is not a P3P policy! See g.co/p3phelp for more info."', 'Content-Encoding': 'gzip', 'Server': 'gws', 'X-XSS-Protection': '0', 'X-Frame-Options': …
Cookies
Можно просмотреть файлы cookie, которые сервер отправляет вам обратно с помощью атрибута .cookies. Запросы также позволяют отправлять свои собственные cookie-файлы.
Чтобы добавить куки в запрос, Вы должны использовать dict, переданный в параметр cookie.
import requests
url = 'https://www.google.ru/'
headers = {'user-agent': 'your-own-user-agent/0.0.1'}
cookies = {'visit-month': 'February'}
response = requests.get(url, headers=headers, cookies=cookies)
print(response.request.headers)
# Вывод:
{'user-agent': 'your-own-user-agent/0.0.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Cookie': 'visit-month=February'}
Редиректы и история
По умолчанию модуль Requests выполняет редиректы для всех HTTP глаголов, кроме HEAD.
Существует возможность использовать параметр history объекта Response, чтобы отслеживать редиректы.
Например, GitHub перенаправляет все запросы HTTP на HTTPS:
import requests
response = requests.get('https://www.google.ru/')
print(response.url)
print(response.status_code)
print(response.history)
# Вывод:
https://www.google.ru/
200
[]
Тайм ауты
Так же легко можно управлять тем, сколько программа будет ждать возврат response. Время ожидания задаётся параметром timeout. Это очень важный параметр, так как, если его не использовать, написанный Вами скрипт может «зависнуть» в вечном ожидании ответа от сервера. Используем предыдущий код:
import requests
response = requests.get(‘https://www.google.ru/’, timeout=0.001)
print(response.url)
print(response.status_code)
print(response.history)
# Вывод:…
raise ConnectTimeout(e, request=request)
requests.exceptions.ConnectTimeout: HTTPSConnectionPool(host=’www.google.ru’, port=443): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPSConnection object at 0x000001E331681C70>, ‘Connection to www.google.ru timed out. (connect timeout=0.001)’))
Модуль не ждёт полной загрузки ответа. Исключение возникает, если сервер не отвечает (хотя бы один байт) за указанное время.
Ошибки и исключения
Если возникнет непредвиденная ситуация – ошибка соединения, модуль Requests выбросит эксепшн ConnectionError.
response.raise_for_status() возвращает объект HTTPError, если в процессе произошла ошибка. Его применяют для отладки модуля и, поэтому, он является неотъемлемой частью запросов Python.
Если выйдет время запроса, вызывается исключение Timeout. Если слишком много перенаправлений, то появится исключение TooManyRedirects.
Прежде чем начать, убедитесь, что установлена последняя версия Requests.
Для начала, давайте рассмотрим простые примеры.
Импортируйте модуль Requests:
Попробуем получить веб-страницу с помощью get-запроса. В этом примере давайте рассмотрим общий тайм-лайн GitHub:
r = requests.get('https://api.github.com/events')
Мы получили объект Response с именем r. С помощью этого объекта можно получить всю необходимую информацию.
Простой API Requests означает, что все типы 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')
Передача параметров в 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('https://httpbin.org/get', params=payload)
print(r.url)
Как видно, URL был сформирован правильно:
https://httpbin.org/get?key2=value2&key1=value1
Ключ словаря, значение которого None, не будет добавлен в строке запроса URL.
Вы можете передать список параметров в качестве значения:
>>> 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
Содержимое ответа (response)
Мы можем прочитать содержимое ответа сервера. Рассмотрим снова тайм-лайн 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. Можно узнать, какую кодировку использует 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.
Бинарное содержимое ответа
Вы можете также получить доступ к телу ответа в виде байтов для не текстовых ответов:
>>> r.content
b'[{"repository":{"open_issues":0,"url":"https://github.com/...
Передача со сжатием gzip и deflate автоматически декодируются для вас.
Например, чтобы создать изображение на основе бинарных данных, возвращаемых при ответе на запрос, используйте следующий код:
from PIL import Image
from io import BytesIO
i = Image.open(BytesIO(r.content))
Содержимое ответа в JSON
Если вы работаете с данными в формате JSON, воспользуйтесь встроенным JSON декодером:
>>> import requests
>>> r = requests.get('https://api.github.com/events')
>>> r.json()
[{'repository': {'open_issues': 0, 'url': 'https://github.com/...
Если декодирование в JSON не удалось, r.json() вернет исключение. Например, если ответ с кодом 204 (No Content), или на случай если ответ содержит не валидный JSON, попытка обращения к r.json() будет возвращать ValueError: No JSON object could be decoded.
Следует отметить, что успешный вызов 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
>>> r.raw.read(10)
'x1fx8bx08x00x00x00x00x00x00x03'
Однако, можно использовать подобный код как шаблон, чтобы сохранить результат в файл:
with open(filename, 'wb') as fd:
for chunk in r.iter_content(chunk_size=128):
fd.write(chunk)
Использование r.iter_content обработает многое из того, с чем бы вам пришлось иметь дело при использовании r.raw напрямую. Для извлечения содержимого при потоковой загрузке, используйте способ, описанный выше. Обратите внимание, что chunk_size можно свободно скорректировать до числа, которое лучше подходит в вашем случае.
Важное замечание об использовании
Response.iter_contentиResponse.raw.Response.iter_contentбудет автоматически декодироватьgzipиdeflate.Response.raw— необработанный поток байтов, он не меняет содержимое ответа. Если вам действительно нужен доступ к байтам по мере их возврата, используйтеResponse.raw.
Пользовательские заголовки
Если вы хотите добавить HTTP заголовки в запрос, просто передайте соответствующий dict в параметре 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=. - Они же будут удалены при редиректе.
- Заголовки авторизации с прокси будут переопределены учетными данными прокси-сервера, которые указаны в вашем URL.
- Content-Length будут переопределены, когда вы определите длину содержимого.
Кроме того, запросы не меняют свое поведение вообще, основываясь на указанных пользовательских заголовках.
Значения заголовка должны быть
string, bytestring или unicode. Хотя это разрешено, рекомендуется избегать передачи значений заголовковunicode.
Более сложные POST запросы
Часто вы хотите послать некоторые form-encoded данные таким же образом, как это делается в HTML форме. Для этого просто передайте соответствующий словарь в аргументе data. Ваш словарь данных в таком случае будет автоматически закодирован как HTML форма, когда будет сделан запрос:
>>> payload = {'key1': 'value1', 'key2': 'value2'}
>>> r = requests.post("https://httpbin.org/post", data=payload)
>>> print(r.text)
{
...
"form": {
"key2": "value2",
"key1": "value1"
},
...
}
Аргумент data также может иметь несколько значений для каждого ключа. Это можно сделать, указав data в формате tuple, либо в виде словаря со списками в качестве значений. Особенно полезно, когда форма имеет несколько элементов, которые используют один и тот же ключ:
>>> 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
Бывают случаи, когда нужно отправить данные не закодированные методом form-encoded. Если вы передадите в запрос строку вместо словаря, эти данные отправятся в не измененном виде.
К примеру, GitHub API v3 принимает закодированные JSON POST/PATCH данные:
import json
url = 'https://api.github.com/some/endpoint'
payload = {'some': 'data'}
r = requests.post(url, data=json.dumps(payload))
Вместо того, чтобы кодировать dict, вы можете передать его напрямую, используя параметр json (добавленный в версии 2.4.2), и он будет автоматически закодирован:
url = 'https://api.github.com/some/endpoint'
payload = {'some': 'data'}
r = requests.post(url, json=payload)
Обратите внимание, параметр json игнорируется, если передаются data или files.
Использование параметра json в запросе изменит заголовок Content-Type на application/json.
POST отправка Multipart-Encoded файла
Запросы упрощают загрузку файлов с многостраничным кодированием (Multipart-Encoded) :
>>> url = 'https://httpbin.org/post'
>>> files = {'file': open('report.xls', 'rb')}
>>> r = requests.post(url, files=files)
>>> r.text
{
...
"files": {
"file": ""
},
...
}
Вы можете установить имя файла, content_type и заголовки в явном виде:
>>> 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": ""
},
...
}
Можете отправить строки, которые будут приняты в виде файлов:
>>> url = 'https://httpbin.org/post'
>>> files = {'file': ('report.csv', 'some,data,to,sendnanother,row,to,sendn')}
>>> 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. Ознакомьтесь с документацией toolbelt для получения более детальной информации о том, как им пользоваться.
Для отправки нескольких файлов в одном запросе, обратитесь к расширенной документации.
Предупреждение!
Настоятельно рекомендуется открывать файлы в бинарном режиме. Это связано с тем, что запросы могут пытаться предоставить для вас заголовок Content-Length, и если это значение будет установлено на количество байтов в файле будут возникать ошибки, при открытии файла в текстовом режиме.
Коды состояния ответа
Мы можем проверить код состояния ответа:
>>> r = requests.get('https://httpbin.org/get')
>>> r.status_code
200
У requests есть встроенный объект вывода кодов состояния:
>>> r.status_code == requests.codes.ok
True
Если мы сделали неудачный запрос (ошибка 4XX или 5XX), то можем вызвать исключение с помощью r.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
Но если 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['Content-Type']
'application/json'
>>> r.headers.get('content-type')
'application/json'
Cookies
Если в запросе есть cookies, вы сможете быстро получить к ним доступ:
>>> url = 'https://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 возвращаются в RequestsCookieJar, который работает как dict, но также предлагает более полный интерфейс, подходящий для использования в нескольких доменах или путях. Словарь с cookie может также передаваться в запросы:
>>> 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 будет выполнять редиректы для всех HTTP глаголов, кроме HEAD.
Мы можем использовать свойство history объекта Response, чтобы отслеживать редиректы .
Список Response.history содержит объекты Response, которые были созданы для того, чтобы выполнить запрос. Список сортируется от более ранних, до более поздних ответов.
Например, GitHub перенаправляет все запросы HTTP на HTTPS:
>>> r = requests.get('https://github.com/')
>>> r.url
'https://github.com/'
>>> r.status_code
200
>>> r.history
[]
Если вы используете запросы GET, OPTIONS, POST, PUT, PATCH или DELETE, вы можете отключить обработку редиректа с помощью параметра allow_redirects:
>>> r = requests.get('https://github.com/', allow_redirects=False)
>>> r.status_code
301
>>> r.history
[]
Если вы используете HEAD, вы также можете включить редирект:
>>> r = requests.head('https://github.com/', allow_redirects=True)
>>> r.url
'https://github.com/'
>>> r.history
[]
Тайм-ауты
Вы можете сделать так, чтобы Requests прекратил ожидание ответа после определенного количества секунд с помощью параметра timeout.
Почти весь код должен использовать этот параметр в запросах. Несоблюдение этого требования может привести к зависанию вашей программы:
>>> requests.get('https://github.com/', timeout=0.001)
Traceback (most recent call last):
File "", line 1, in
requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)
Timeout это не ограничение по времени полной загрузки ответа. Исключение возникает, если сервер не дал ответ за timeout секунд (точнее, если ни одного байта не было получено от основного сокета за timeout секунд).
Ошибки и исключения
В случае неполадок в сети (например, отказа DNS, отказа соединения и т.д.), Requests вызовет исключение ConnectionError.
Response.raise_for_status() вызовет HTTPError если в запросе HTTP возникнет статус код ошибки.
Если выйдет время запроса, вызывается исключение Timeout. Если запрос превышает заданное значение максимального количества редиректов, то вызывают исключение TooManyRedirects.
Все исключения, которые вызывает непосредственно Requests унаследованы от requests.exceptions.RequestException.
Тест на знание основ Requests
Какой из HTTP-запросов является правильным?
requests.post(url, data={‘key’:’value’})
requests.get(url, params={‘key’:’value’})
requests.post(url, params={‘key’:’value’})
Какое из утверждений верно?
response.json() возвращает содержимое ответа в виде объекта dict
response.text возвращает содержимое ответа в виде байтового объекта
response.content возвращает содержимое ответа в виде строкового объекта
response.json возвращает содержимое ответа в виде объекта dict
Как получить куки из ответа на запрос?
Что вернет метод status_code объекта Response?
Ошибку, если запрос неудачный
Какой код сформирует url «https://test.com/page?key1=value1&key2=value21,value22»
r.get(‘https://test.com/page’, params={‘key1’: ‘value1’, ‘key2’: [‘value21’, ‘value22’]})
r.get(‘https://test.com/page’, params={‘key1’: ‘value1’, ‘key2’: ‘value21,value22’})
r.get(‘https://test.com/page’, params={‘key1’: ‘value1’, ‘key2’: ‘value21’, ‘key2’: ‘value22’})
Перевод статьи Python’s Requests Library (Guide) от замечательной команды Real Python, которая уже не в первый раз радует нас новыми интересными и полезными материалами о языке Python. И хотя в сети есть ее переводы, я немного адаптировал ее содержимое, добавив некоторые справочные данные. Так же я добавил подраздел об использовании механизма сессий при обращении к удаленным службам, которого как я считаю не хватало в оригинале статьи для понимания некоторых затрагиваемых вопросов.
Библиотека requests в настоящее время уже давно является стандартом де-факто для реализации отправки HTTP-запросов в Python. Она элегантно абстрагирует сложность написания запросов к серверу, используя красивый и простой API, так что вы можете спокойно сосредоточиться при работе над вашим приложении на вопросах организации взаимодействия с различными удаленными службами, а также дальнейшей обработки получаемых данных.
В этой статье мы рассмотрим лишь некоторые из наиболее полезных возможностей, которые может предложить requests, а также основные способы настройки ее инструментов и оптимизации их работы для различных ситуаций, с которыми вы можете столкнуться в дальнейшем. Вы также ознакомитесь с тем, как эффективно использовать requests для предотвращения замедления работы (зависания) ваших приложений при взаимодействии с удаленными службами.
В этом руководстве вы узнаете, как:
- отправлять запросы, используя самые распространенные HTTP методы;
- настроить содержимое заголовков и отправляемых данных запросов, используя строку запроса и текст сообщения;
- просмотреть содержимое ваших запросов и ответов;
- отправлять на сервер аутентифицированные запросы;
- корректно настроить параметры запросов таким образом, чтобы избежать резервного копирования данных или замедления работы вашего приложения.
Хотя я попытался включить в настоящее руководство столько информации, сколько нужно, чтобы понять все примеры кода, которые я включил в эту статью, но тем не менее я надеюсь, что для начала вы обладаете общими базовыми понятиями о работе протокола HTTP.
Содержание
- Начинаем работать с requests
- Запрос GET (GET request)
- Ответ (response)
- Код состояния запроса (status codes)
- Содержимое ответа (Content)
- Заголовки ответа (Headers)
- Параметры строки запроса (Query String Parameters)
- Заголовки запроса (Request Headers)
- Другие HTTP методы запроса
- Тело сообщения запроса
- Инспектируем отправленный запрос
- Использование механизма сессий (Session)
- Аутентификация
- Проверка подлинности SSL сертификата (SSL Certificate Verification)
- Производительность
- Тайм-ауты Timeouts
- Объект сессии Session
- Количество повторов запроса
- Заключение
И так начнем с установки библиотеки requests. Для этого выполните в терминале консоли следующую команду:
$ pip install requests
Если же вы предпочитаете использовать Pipenv для управления пакетами Python, то можете запустить на исполнение следующую команду:
$ pipenv install requests
Установка библиотеки
requestsв операционной системе Windows и Linux практически не отличаются. В сети достаточно материалов, в которых описан принцип работы с пакетами Python в обеих системах. Поэтому в этой статье касаться этих вопросов мы далее не будем.
После того, как мы установили requests, мы можем ее использовать в своем приложении. Импорт библиотеки requests в ваш код выглядит следующим образом:
import requests
Теперь, когда мы все подготовили, пришло время начинать наш путь по изучению основ работы с библиотекой requests. И наша первая цель научиться отправлять HTTP-запрос GET.
Запрос GET (GET request)
Такие методы запросов, как GET и POST, определяют, действие, которое вы пытаетесь выполнить при выполнении HTTP-запроса. Помимо GET и POST, есть еще несколько других достаточно распространенных методов запросов и их мы рассмотрим в этом руководстве позже.
И так одним из самых распространенных методов HTTP-запросов является GET. Метод GET предписывает, что вы пытаетесь получить или извлечь некоторые данные из указанного ресурса. Чтобы отправить GET запрос, необходимо вызвать метод requests.get(url).
Проверим это на практике: отправим GET-запрос к GitHub Root REST API, вызвав метод get() со следующим значением параметра url:
>>> requests.get('https://api.github.com')
<Response [200]>
Отлично! Мы отправили свой первый запрос. Давайте изучим содержимое полученного ответа.
Ответ (response)
Объект Response является мощным средством для просмотра содержимого и обработки результатов наших запросов. Давайте пошлем, рассмотренный нами выше, запрос GET еще раз, но в этот раз сохраним принятое значение с объектом ответа в переменной, и затем поближе познакомиться с его атрибутами (свойствами), а также поведением:
response = requests.get('https://api.github.com')
В этом примере кода, мы используя возможности библиотеки requests, перехватили содержимое ответа удаленного сервера, возвращаемое методом get(). И далее его значение, которое находится в экземпляре объекта Response, сохраним в переменную с именем response. Теперь мы можем использовать response для того, чтобы получить различную информацию о результатах нашего GET запроса .
Код состояния запроса (status codes)
Первым битом информации, которую вы можете получить от объекта ответа Response, является код состояния запроса к серверу status codes. Код состояния информирует вас о статусе нашего запроса.
Например, статус 200 OK означает, что ваш запрос был успешным, а статус 404 NOT FOUND означает, что искомый ресурс не найден. Существует много других кодов состояния запроса, которые могут дать вам более детальное представление о том, что же все таки произошло с отправленным запросом.
Используя свойство объекта response.status_code, мы можем получить доступ к коду состояния ответа, который вернул удаленный сервер:
>>> response.status_code 200
При обращении к свойству .status_code мы получили значение 200, что означает, что наш запрос был успешным, и сервер отправил нам данные, которые мы запрашивали.
В большинстве случаев эту информацию мы будем использовать для реализации в своем коде различной логики: управлять дальнейшей работой нашего приложения:
if response.status_code == 200:
print('Success!')
elif response.status_code == 404:
print('Not Found.')
В соответствии с логикой этого примера кода, если сервер возвращает код состояния 200, то наша программа напечатает «Success!». Если же — 404, то напечатает “Not Found”.
Библиотека requests существенно упрощает процесс взаимодействия вашего приложения с сервером. Однако необходимо знать ее некоторые довольно специфические особенности. Так например, если мы используем экземпляр объекта Response в условном выражении, то его логическое значение приравнивается к True, если был получен код состояния запроса в диапазоне от 200 до 400, и только в противном случае False.
Поэтому мы можем упростить последний пример, переписав код оператора if следующим образом:
if response:
print('Success!')
else:
print('An error has occurred.')
Маленькая техническая деталь: этот тест на истинность значения показал такой результат возможным, так как в объекте
Responseспециальный метод класса__bool__()переопределен.Это означает то, что поведение по умолчанию объекта
Responseпри вычислении его логического значения было переопределено для процедуры проверки кода состояния запроса.
Имейте в виду, что этот способ проверки не гарантирует, что код состояния вашего запроса успешен и равен 200. Причиной прохождения проверки на истинность является то, что запрос может получать и другие “успешные” коды состояния в диапазоне от 200 до 400. Такие, например, как 204 NO CONTENT и 304 NOT MODIFIED, которые также можно считать в определенном смысле “успешными”, так как они определяют некоторый успешно обработанный сервером ответ на запрос. Например, код 204 информирует нас, что запрос был успешным, но тело сообщения ответа сервера не ничего не содержит.
Поэтому используете этот способ проверки, если захотите узнать, был ли запрос в целом успешным, а только затем, при необходимости, обработать содержимое ответа соответствующим образом на основе значения его кода состояния.
Допустим, вы не хотите проверять код состояния ответа в операторе if. Вместо этого вы можете генерировать исключение специального типа HTTPError, если запрос был неудачен. Сделать это можно используя метод .raise_for_status() следующим образом:
import requests
from requests.exceptions import HTTPError
for url in ['https://api.github.com', 'https://api.github.com/invalid']:
try:
response = requests.get(url)
# Если запрос был успешен, то исключение Exception не возбуждается
response.raise_for_status()
except HTTPError as http_err:
print(f'HTTP error occurred: {http_err}') # Python 3.6
except Exception as err:
print(f'Other error occurred: {err}') # Python 3.6
else:
print('Success!')
Таким образом, если вы вызываете метод .raise_for_status(), то исключение типа HTTPError будет вызываться для определенных кодов состояния (не успешных запросов к серверу). И если код состояния определяет запрос как успешный, то приложение продолжит работу без возбуждения этого исключения.
Для дальнейшего чтения: если вам не знакомы f-строки Python 3.6, то я призываю вас срочно познакомится с ими, поскольку они являются отличным способом упростить использование строковых шаблонов.
И так мы изучили некоторые приемы работы с кодами состояния ответов сервера. Однако, когда мы посылаем GET запрос, нам редко нужна информация лишь о коде состояния. Обычно мы хотим получить нечто больше. Далее мы научимся получать и обрабатывать содержимое данных, которые сервер отправляет обратно в теле ответа.
Содержимое ответа (Content)
Ответ на успешный запрос GET часто содержит в сообщении некоторую ценную информацию, известную так же, как полезное содержимое (payload). Используя атрибуты и методы объекта Response, мы можем просматривать его содержимое в различных форматах.
Чтобы получить содержимое ответа в бинарном виде, мы можем использовать свойство Response.content:
>>> response = requests.get('https://api.github.com')
>>> response.content
b'{"current_user_url":"https://api.github.com/user","current_user_authorizations_html_url":"https://github.com/settings/connections/applications{/client_id}","authorizations_url":"https://api.github.com/authorizations","code_search_url":"https://api.github.com/search/code?q={query}{&page,per_page,sort,order}","commit_search_url":"https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}","emails_url":"https://api.github.com/user/emails","emojis_url":"https://api.github.com/emojis","events_url":"https://api.github.com/events","feeds_url":"https://api.github.com/feeds","followers_url":"https://api.github.com/user/followers","following_url":"https://api.github.com/user/following{/target}","gists_url":"https://api.github.com/gists{/gist_id}","hub_url":"https://api.github.com/hub","issue_search_url":"https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}","issues_url":"https://api.github.com/issues","keys_url":"https://api.github.com/user/keys","notifications_url":"https://api.github.com/notifications","organization_repositories_url":"https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}","organization_url":"https://api.github.com/orgs/{org}","public_gists_url":"https://api.github.com/gists/public","rate_limit_url":"https://api.github.com/rate_limit","repository_url":"https://api.github.com/repos/{owner}/{repo}","repository_search_url":"https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}","current_user_repositories_url":"https://api.github.com/user/repos{?type,page,per_page,sort}","starred_url":"https://api.github.com/user/starred{/owner}{/repo}","starred_gists_url":"https://api.github.com/gists/starred","team_url":"https://api.github.com/teams","user_url":"https://api.github.com/users/{user}","user_organizations_url":"https://api.github.com/user/orgs","user_repositories_url":"https://api.github.com/users/{user}/repos{?type,page,per_page,sort}","user_search_url":"https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"}'
Несмотря на то, что свойство Response.content предоставляет нам доступ к “сырым” байтам полезного содержимого ответа и в большинстве случаев мы будем преобразовывать их в строку с заданной кодировкой символов, например, UTF-8. Объект Response легко сделает это для нас, предоставляя доступ к свойству Response.text:
>>> response.text
'{"current_user_url":"https://api.github.com/user","current_user_authorizations_html_url":"https://github.com/settings/connections/applications{/client_id}","authorizations_url":"https://api.github.com/authorizations","code_search_url":"https://api.github.com/search/code?q={query}{&page,per_page,sort,order}","commit_search_url":"https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}","emails_url":"https://api.github.com/user/emails","emojis_url":"https://api.github.com/emojis","events_url":"https://api.github.com/events","feeds_url":"https://api.github.com/feeds","followers_url":"https://api.github.com/user/followers","following_url":"https://api.github.com/user/following{/target}","gists_url":"https://api.github.com/gists{/gist_id}","hub_url":"https://api.github.com/hub","issue_search_url":"https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}","issues_url":"https://api.github.com/issues","keys_url":"https://api.github.com/user/keys","notifications_url":"https://api.github.com/notifications","organization_repositories_url":"https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}","organization_url":"https://api.github.com/orgs/{org}","public_gists_url":"https://api.github.com/gists/public","rate_limit_url":"https://api.github.com/rate_limit","repository_url":"https://api.github.com/repos/{owner}/{repo}","repository_search_url":"https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}","current_user_repositories_url":"https://api.github.com/user/repos{?type,page,per_page,sort}","starred_url":"https://api.github.com/user/starred{/owner}{/repo}","starred_gists_url":"https://api.github.com/gists/starred","team_url":"https://api.github.com/teams","user_url":"https://api.github.com/users/{user}","user_organizations_url":"https://api.github.com/user/orgs","user_repositories_url":"https://api.github.com/users/{user}/repos{?type,page,per_page,sort}","user_search_url":"https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"}'
Поскольку для декодирования байтов bytes в строку str требуется схема кодирования, то requests в начале попытается угадать кодировку содержимого ответа на основе его заголовков Content-Type, в том если вы предварительно их не укажете. Вы также можете указать кодировку явно, установив значение свойства объекта Response.encoding перед обращением к Response.text:
>>> response.text
'{"current_user_url":"https://api.github.com/user","current_user_authorizations_html_url":"https://github.com/settings/connections/applications{/client_id}","authorizations_url":"https://api.github.com/authorizations","code_search_url":"https://api.github.com/search/code?q={query}{&page,per_page,sort,order}","commit_search_url":"https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}","emails_url":"https://api.github.com/user/emails","emojis_url":"https://api.github.com/emojis","events_url":"https://api.github.com/events","feeds_url":"https://api.github.com/feeds","followers_url":"https://api.github.com/user/followers","following_url":"https://api.github.com/user/following{/target}","gists_url":"https://api.github.com/gists{/gist_id}","hub_url":"https://api.github.com/hub","issue_search_url":"https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}","issues_url":"https://api.github.com/issues","keys_url":"https://api.github.com/user/keys","notifications_url":"https://api.github.com/notifications","organization_repositories_url":"https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}","organization_url":"https://api.github.com/orgs/{org}","public_gists_url":"https://api.github.com/gists/public","rate_limit_url":"https://api.github.com/rate_limit","repository_url":"https://api.github.com/repos/{owner}/{repo}","repository_search_url":"https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}","current_user_repositories_url":"https://api.github.com/user/repos{?type,page,per_page,sort}","starred_url":"https://api.github.com/user/starred{/owner}{/repo}","starred_gists_url":"https://api.github.com/gists/starred","team_url":"https://api.github.com/teams","user_url":"https://api.github.com/users/{user}","user_organizations_url":"https://api.github.com/user/orgs","user_repositories_url":"https://api.github.com/users/{user}/repos{?type,page,per_page,sort}","user_search_url":"https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"}'
Если вы обратите внимание на полученное содержимое ответа, то увидите, что оно представляет собой сериализованную строку в формате JSON. Поэтому для того, чтобы в результате получить словарь, вы можете взять строку str, полученную из свойства Response.text, и десериализовать ее с помощью метода json.loads(). Однако более простой способ выполнить эту задачу — использовать метод нашего объекта Response.json():
>>> response.json()
{'current_user_url': 'https://api.github.com/user', 'current_user_authorizations_html_url': 'https://github.com/settings/connections/applications{/client_id}', 'authorizations_url': 'https://api.github.com/authorizations', 'code_search_url': 'https://api.github.com/search/code?q={query}{&page,per_page,sort,order}', 'commit_search_url': 'https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}', 'emails_url': 'https://api.github.com/user/emails', 'emojis_url': 'https://api.github.com/emojis', 'events_url': 'https://api.github.com/events', 'feeds_url': 'https://api.github.com/feeds', 'followers_url': 'https://api.github.com/user/followers', 'following_url': 'https://api.github.com/user/following{/target}', 'gists_url': 'https://api.github.com/gists{/gist_id}', 'hub_url': 'https://api.github.com/hub', 'issue_search_url': 'https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}', 'issues_url': 'https://api.github.com/issues', 'keys_url': 'https://api.github.com/user/keys', 'notifications_url': 'https://api.github.com/notifications', 'organization_repositories_url': 'https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}', 'organization_url': 'https://api.github.com/orgs/{org}', 'public_gists_url': 'https://api.github.com/gists/public', 'rate_limit_url': 'https://api.github.com/rate_limit', 'repository_url': 'https://api.github.com/repos/{owner}/{repo}', 'repository_search_url': 'https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}', 'current_user_repositories_url': 'https://api.github.com/user/repos{?type,page,per_page,sort}', 'starred_url': 'https://api.github.com/user/starred{/owner}{/repo}', 'starred_gists_url': 'https://api.github.com/gists/starred', 'team_url': 'https://api.github.com/teams', 'user_url': 'https://api.github.com/users/{user}', 'user_organizations_url': 'https://api.github.com/user/orgs', 'user_repositories_url': 'https://api.github.com/users/{user}/repos{?type,page,per_page,sort}', 'user_search_url': 'https://api.github.com/search/users?q={query}{&page,per_page,sort,order}'}
Отлично, тип возвращаемого методом Response.json() значения словарь, поэтому мы можем, как обычно, получить доступ к его значениям по соответствующему ключу.
Вы можете по разному использовать информацию о коде состояния и содержимое полученного ответа сервера, но если нам нужна дополнительная информация, такая, например, как метаданные о самом ответе, то вам нужно иметь дело с заголовками полученного ответа.
Заголовки ответа сервера могут дать много полезной информации, такой, например, как тип полезного содержимого ответа, ограничение по времени, в течение которого ответ будет кэшироваться и т.д. Чтобы просмотреть содержимое заголовков, необходимо обратиться к свойству объекта Response.headers:
>>> response.headers
{'Server': 'GitHub.com', 'Date': 'Mon, 10 Dec 2018 17:49:54 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Status': '200 OK', 'X-RateLimit-Limit': '60', 'X-RateLimit-Remaining': '59', 'X-RateLimit-Reset': '1544467794', 'Cache-Control': 'public, max-age=60, s-maxage=60', 'Vary': 'Accept', 'ETag': 'W/"7dc470913f1fe9bb6c7355b50a0737bc"', 'X-GitHub-Media-Type': 'github.v3; format=json', 'Access-Control-Expose-Headers': 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type', 'Access-Control-Allow-Origin': '*', 'Strict-Transport-Security': 'max-age=31536000; includeSubdomains; preload', 'X-Frame-Options': 'deny', 'X-Content-Type-Options': 'nosniff', 'X-XSS-Protection': '1; mode=block', 'Referrer-Policy': 'origin-when-cross-origin, strict-origin-when-cross-origin', 'Content-Security-Policy': "default-src 'none'", 'Content-Encoding': 'gzip', 'X-GitHub-Request-Id': 'E439:4581:CF2351:1CA3E06:5C0EA741'}
При обращении к свойству Response.headers будет возвращен схожий со словарем объект, позволяющий получить доступ к значениям заголовков полученного ответа по ключу. Например, чтобы определить тип полезного содержимого ответа, получаем доступ к значению заголовка Content-Type:
>>> response.headers['Content-Type'] 'application/json; charset=utf-8'
У этого, как мы уже говорили, схожим со словарем объекте заголовков есть еще одна особенность. Спецификация HTTP определяет названия заголовков без учета регистра, это означает, что мы можем получить доступ к их значениям, совершенно не беспокоясь об регистре их наименований:
>>> response.headers['content-type'] 'application/json; charset=utf-8'
Используете ли вы ключ 'content-type' или 'Content-type', вы получите одно и то же корректное значение.
Теперь когда мы ознакомились с основами работы с объектом Response , рассмотрели его наиболее полезные свойства и методы в действии. Давайте сделаем шаг назад и посмотрим, как изменяются ответы сервера при использовании параметров запроса GET , передаваемых в строке запроса.
Параметры строки запроса (Query String Parameters)
Одним из самых распространенных способов настройки запроса GET является передача серверу данных в URL строки запроса.
Обратите внимание, что GET запрос не имеет тела сообщения. Но, это не означает, что с его помощью мы не можем передать серверу никакую информацию. Это можно делать с помощью специальных GET параметров. Чтобы добавить GET параметры к запросу, нужно в конце URL-адреса поставить знак
?и после него начинать задавать их по следующему правилу:имя_параметра1=значение_параметра1&имя_параметра2=значение_параметра2. Разделителем между параметрами служит знак&.
Для передачи GET параметров запроса необходимо передать нужную информацию в именованный параметр params метода get(). Например, следующим способом вы можете использовать Search API GitHub для более узкого поиска репозитория библиотеки requests:
import requests
# Поиск requests в репозиториях GitHub
response = requests.get(
'https://api.github.com/search/repositories',
params={'q': 'requests+language:python'},
)
# Просматриваем значения атрибутов результатов поиска репозитория requests
json_response = response.json()
repository = json_response['items'][0]
print(f'Repository name: {repository["name"]}') # Python 3.6+
print(f'Repository description: {repository["description"]}') # Python 3.6+
Передав словарь {'q': 'requests+language: python'} в качестве значения параметра params метода .get(), мы таким образом скорректировали результаты, возвращаемые Search API.
Мы можем передавать значения в params метода get() как в виде словаря, как мы это только что сделали, так и в виде списка кортежей:
>>> requests.get(
... 'https://api.github.com/search/repositories',
... params=[('q', 'requests+language:python')],
... )
<Response [200]>
Так же можно передать данные в бинарном виде bytes:
>>> requests.get( ... 'https://api.github.com/search/repositories', ... params=b'q=requests+language:python', ... ) <Response [200]>
Строка запроса используется для передачи параметров в GET запросах. Еще одним способом управления запросами к удаленным службам, является добавление или изменение отправляемых в них заголовков.
Для того, чтобы используя requests, настроить содержимое заголовков запроса, необходимо передать словарь с соответствующими ключами и значениями HTTP-заголовков в метод .get(), а точнее в его именованный параметр headers. Например, изменим свой предыдущий поисковый запрос: добавим ключевые слова для уточнения результатов поиска, указав точно text-match текстовое соответствие для поиска (тип контента) в заголовок Accept:
import requests
response = requests.get(
'https://api.github.com/search/repositories',
params={'q': 'requests+language:python'},
headers={'Accept': 'application/vnd.github.v3.text-match+json'},
)
# выведем в консоли массив всех совпадений `text-matches`,
# которые мы задали для уточнения результатов поиска
json_response = response.json()
repository = json_response['items'][0]
print(f'Text matches: {repository["text_matches"]}')
Заголовок Accept сообщает серверу, какие типы контента может обрабатывать ваше приложение. В нашем случае, поскольку вы хотите, что бы учитывались дополнительные уточняющие параметры поиска, вы устанавливаете для этого заголовка значение application / vnd.github.v3.text-match + json. Это значение является проприетарным для заголовка Accept, то есть зарезервированным GitHub для подобных случаев, и указывает на поиск содержимого в формате JSON.
Прежде чем мы ознакомимся с другими способами настройки ваших запросов, давайте слегка расширим свой кругозор, изучив другие HTTP методы, а точнее как с ними работать в requests.
Другие HTTP методы запроса
Помимо GET существуют и другие часто используемые HTTP методы, например, POST, PUT, DELETE, HEAD, PATCH и OPTIONS. И библиотека requests ожидаемо предоставляет методы, со схожей как у метода get() нотацией использования, для отправки и управления настройками каждого из этих HTTP методов запросов:
>>> requests.post('https://httpbin.org/post', data={'key':'value'})
>>> requests.put('https://httpbin.org/put', data={'key':'value'})
>>> requests.delete('https://httpbin.org/delete')
>>> requests.head('https://httpbin.org/get')
>>> requests.patch('https://httpbin.org/patch', data={'key':'value'})
>>> requests.options('https://httpbin.org/get')
В примере ниже каждый вызов соответствующего метода посылает запрос сервису httpbin, используя различные HTTP методы. И мы так же, как и ранее можем просмотреть содержимое их ответов:
>>> response = requests.head('https://httpbin.org/get')
>>> response.headers['Content-Type']
'application/json'
>>> response = requests.delete('https://httpbin.org/delete')
>>> json_response = response.json()
>>> json_response['args']
{}
Содержимое заголовков, полезного содержимого ответов, коды состояния и многие другие данные возвращаются с объектом Response для каждого вашего запроса, отправленного любым из методов.
Тело сообщения запроса
В соответствии со спецификацией HTTP протоколов, такие методы отправки запросов как POST, PUT, а так же другие менее распространенные, например PATCH, передают информацию в содержимом сообщения, а не через параметры строки запроса. Используя requests, вы можете передать полезную информацию в сообщении запроса через именованный параметр data соответствующего метода отправки.
Таким образом все отправляемые данные содержатся в самом теле вашего запроса. Кроме того, методом POST на удаленный сервер нередко загружаются файлы.
Параметр data принимает словарь, список кортежей, байтов или файлоподобный объект. В каком конкретно виде посылать данные в теле запроса вы должны выбрать самостоятельно, исходя из требований удаленной службы (сервера) с которой хотите взаимодействовать.
Например, если тип содержимого вашего запроса application / x-www-form-urlencoded, то вы можете отправить данные html формы в виде словаря:
>>> requests.post('https://httpbin.org/post', data={'key':'value'})
<Response [200]>
Вы можете отправить те же данные в виде списка кортежей следующим образом:
>>> requests.post('https://httpbin.org/post', data=[('key', 'value')])
<Response [200]>
В теле сообщения значения кодируются в виде кортежа с ключами, разделенными символом
'&', и'='между ключом и значением.
Если вам необходимо отправить данные в формате JSON, то вы должны использовать для передачи соответствующий именованный параметр json. При отправке запроса с содержимым JSON библиотека requests сама сериализует ваши данные и добавит соответствующее значение для заголовка Content-Type.
httpbin.org — отличный вспомогательный ресурс, созданный автором requests Кеннетом Рейтцем. Это сервис принимает тестовые запросы и отправляет в ответ информацию о них. Например, вы можете использовать его для проверки корректности вашего POST-запроса:
>>> response = requests.post('https://httpbin.org/post', json={'key':'value'})
>>> json_response = response.json()
>>> json_response['data']
'{"key": "value"}'
>>> json_response['headers']['Content-Type']
'application/json'
Из содержимого ответа видно, что сервер успешно обработал данные вашего запроса и послал ответ с соответствующими заголовками. Библиотека requests предоставляет возможность сохранять настройки посылаемых запросов в виде специального объекта PreparedRequest.
Инспектируем отправленный запрос
Когда вы делаете запрос, библиотека requests предварительно готовит запрос, прежде чем отправить его на целевой сервер. Подготовка запроса включает в себя такие вещи, как проверка корректности заголовков и сериализация содержимого в формате JSON.
Вы можете просмотреть содержимое подготовленного к отправке запроса, используя объект типа PreparedRequest, обратившись к свойству объекта Response.request:
>>> response = requests.post('https://httpbin.org/post', json={'key':'value'})
>>> response.request.headers['Content-Type']
'application/json'
>>> response.request.url
'https://httpbin.org/post'
>>> response.request.body
b'{"key": "value"}'
Просмотр содержимого PreparedRequest дает вам доступ ко всей информации о выполненном запросе, такой как полезное содержимое, URL, отправленные заголовки, данные аутентификации и многое другое.
В соответствии с официальной документацией объект типа PreparedRequest может использоваться и до отправки запроса. Так в следующим примере кода вначале с помощью конструктора Request(method, url, data=data, headers=headers) создается объект запроса, а затем с использованием метода Request.prepare() создается подготовленный запрос. Объект PreparedRequest может в дальнейшем, перед своей непосредственно отправкой, редактироваться в соответствии с логикой вашего приложения, а только затем отправлен с вызовом метода .send().
from requests import Request, Session
s = Session()
req = Request('POST', url, data=data, headers=headers)
prepped = req.prepare()
prepped.body = 'No, I want exactly this as the body.'
del prepped.headers['Content-Type']
resp = s.send(prepped,
stream=stream,
verify=verify,
proxies=proxies,
cert=cert,
timeout=timeout
)
print(resp.status_code)
В этом примере кода мы можем заметить не знакомую нам строку кода: s = Session(). Дело в том, что в этом примере подготовленный запрос использовался для работы с сессионными cookie. В оригинале статьи автор почему-то опустил эту немаловажную возможность библиотеки requests: использование механизма сессий для работы с удаленными службами. Поэтому в следующем разделе мы с ним просто обязаны познакомиться ;).
Использование механизма сессий (Session)
Для организации сессионного взаимодействия вашего приложения с сервером используется объект Session, который позволяет сохранять необходимые настройки для отправки данных от запроса к запросу в течение всей текущей сессии. То есть он сохраняет и задает содержимое файлов cookie во всех запросах, сформированных с использованием текущего экземпляра объекта Session, и гарантирует использование выделенного пула соединений при дальнейшем взаимодействии с сервером. Объект Sesson поддерживает любые методы запросов из состава API requests.
И так давайте сохраним некоторые данные cookie в запросе:
import requests
s = requests.Session()
s.get('https://httpbin.org/cookies/set/sessioncookie/123456789')
r = s.get('https://httpbin.org/cookies')
print(r.text)
# '{"cookies": {"sessioncookie": "123456789"}}'
В примере кода выше мы обратились к API httpbin.org для установки и поддержания соединения в течение сессии с указанным значением sessioncookie.
Механизм сессий также можно применять для предварительной установки значений некоторых настроек по умолчанию и дальнейшем их использовании при отправке запросов. Это делается путем изменения соответствующих свойств объекта Session:
import requests
s = requests.Session()
s.auth = ('user', 'pass')
s.headers.update({'x-test': 'true'})
# both 'x-test' and 'x-test2' are sent
s.get('https://httpbin.org/headers', headers={'x-test2': 'true'})
Все данные, которые вы затем будете передавать методу запроса, будут объединены с установленными значениями текущего сеанса сессии. При этом те параметры, которые вы будете передавать в метод запроса переопределят параметры сеанса сессии, которые мы задали по умолчанию.
Обратите внимание, что параметры, которые мы передаем в метод перед отправкой, не будут сохраняться между отдельными запросами, даже при работе в текущем сеансе сессии, даже если мы используем один и тот же тип метода запроса (в примере ниже это GET запрос). Как видно из результатов выполнения кода из примера ниже: мы отправляем данные cookie, передавая их методу отправки, только с первым запросом GET, но не со вторым:
import requests
s = requests.Session()
r = s.get('https://httpbin.org/cookies', cookies={'from-my': 'browser'})
print(r.text)
# '{"cookies": {"from-my": "browser"}}'
r = s.get('https://httpbin.org/cookies')
print(r.text)
# '{"cookies": {}}'
Если вы хотите непосредственно управлять файлами cookie в течение текущего сеанса, используйте методы утилиты объекта Cookie, для установки соответствующих свойств объекта Session.cookies. Следующий пример кода иллюстрирует способ посылки двух запросов в течение сессии с заданными значениями файлов cookies:
import requests
s = requests.Session()
s.cookies.set('from-my', 'browser')
r = s.get('https://httpbin.org/cookies')
print(r.text)
# '{"cookies": {"from-my": "browser"}}'
r = s.get('https://httpbin.org/cookies')
print(r.text)
# '{"cookies": {"from-my": "browser"}}'
И так мы рассмотрели несколько разновидностей отправляемых запросов, однако у них есть одно ограничение: они не позволяют отправлять аутентифицированные запросы к публичным API. Многие удаленные службы, с которыми вы можете столкнуться, требуют, чтобы вы каким-либо образом предоставляли аутентификационную информацию о себе.
Аутентификация
Как вам уже, вероятно, известно аутентификация по сути помогает удаленному сервису понять, кто вы. Как правило, вы предоставляете серверу свои учетные данные, передавая их через содержимое заголовка авторизации Authorization или любого другого пользовательского заголовка, заранее определенного удаленной службой. Все методы отправки запросов, с которыми мы ознакомились ранее, предоставляют пользователю возможность передавать значение именованному параметру auth, который позволяет передавать учетные данные для авторизации.
Одним из примеров такого API, взаимодействие с которым требуется аутентификация, является GitHub Authenticated User API. Эта точка доступа сервиса GitHub предоставляет информацию о профиле зарегистрированных пользователей. Чтобы сделать запрос к API авторизованного пользователя, вы можете передать свои имя пользователя GitHub и пароль в метод get(), предварительно упаковав их в кортеж:
>>> from getpass import getpass
>>> requests.get('https://api.github.com/user', auth=('username', getpass()))
<Response [200]>
Запрос будет успешно обработан сервером, при условии что учетные данные, которые вы передали в кортеже параметру auth, действительны. Если же вы попытаетесь передать этот запрос без учетных данных, то увидите, что результатом его выполнения будет получение ответа с кодом состояния 401 Unauthorized:
>>> requests.get('https://api.github.com/user')
<Response [401]>
Когда вы передаете параметру auth свои данные (имя пользователя и пароль) , при отправке запроса библиотека requests учитывает эти учетные данные, используя основную схему аутентификации доступа Basic access authentication scheme HTTP протокола.
Следующим способом с помощью объекта HTTPBasicAuth, вы можете послать тот же запрос, но уже явно передав свои учетные данные, используя механизм Basic authentication:
>>> from requests.auth import HTTPBasicAuth
>>> from getpass import getpass
>>> requests.get(
... 'https://api.github.com/user',
... auth=HTTPBasicAuth('username', getpass())
... )
<Response [200]>
В общем случае можно конечно явно не указывать Basic authentication в качестве основного способа аутентификации при отправке вашего запроса, вы можете реализовать эту процедуру любым другим способом. requests так же предоставляет другие методы аутентификации “из коробки”, например, дайджест-аутентификация HTTPDigestAuth или прокси-аутентификация HTTPProxyAuth.
Вы даже можете разработать свой собственный механизм аутентификации с удаленной службой и затем использовать его в ваших приложениях. Для этого вы должны сначала создать подкласс от основного AuthBase, а затем реализовать в объявлении своего класса собственный метод __call __ ():
import requests
from requests.auth import AuthBase
class TokenAuth(AuthBase):
"""Реализуем собственный способ аутентификации"""
def __init__(self, token):
self.token = token
def __call__(self, r):
"""Подключаем к токену API свой заголовок аутентификации"""
r.headers['X-TokenAuth'] = f'{self.token}' # Python 3.6+
return r
requests.get('https://httpbin.org/get', auth=TokenAuth('12345abcde-token'))
Здесь ваш настраиваемый механизм TokenAuth получает токен, а затем включает этот токен в заголовок X-TokenAuth вашего запроса.
При реализации собственных способов аутентификации помните, что простые механизмы проверки подлинности могут привести к уязвимостям безопасности, поэтому, если удаленной службе по какой-либо причине не требуется настраиваемый механизм проверки подлинности, всегда используйте проверенные схемы аутентификации, такие как Basic authentication или OAuth.
И так пока мы задумались о безопасности, давайте рассмотрим вопросы применения в ваших запросах SSL-сертификатов.
Проверка подлинности SSL сертификата (SSL Certificate Verification)
Всякий раз, когда данные, которые вы пытаетесь отправить или получить, являются конфиденциальными, вы начинаете думать о вопросах их безопасности. Вы общаетесь с защищенными сайтами используя протокол HTTP, устанавливая шифрованное соединение с использованием SSL, это означает, что проверка SSL-сертификата целевого сервера имеет решающее значение.
Хорошей новостью является то, что используя в ваших проектах библиотеку requests, она делает это для вас по умолчанию. Тем не менее в некоторых случаях вы можете захотеть изменить или немножко подкорректировать это поведение.
И так если вы захотите отключить проверку SSL-сертификата на удаленном сервере, достаточно передать значение False в именованный параметр verify:
>>> requests.get('https://api.github.com', verify=False)
InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
InsecureRequestWarning
<Response [200]>
Запрос успешен, и как мы видим, requests даже предупреждают вас о том, что вы отправили небезопасный запрос, чтобы помочь вам сохранить ваши данные в безопасности!
Примечание:
requestsиспользует другой отдельный пакетcertifiдля предоставления сертификатов. Это позволяет библиотекеrequestsзнать, каким организациям, выпускающим сертификаты, можно доверять. Поэтому вам следует как можно чаще обновлять пакетcertifi, установленный в вашей системе, чтобы обеспечить максимальную безопасность ваших соединений.
Производительность
При использовании requests, особенно при работе ваших приложений непосредственно в production environment среде, важно учитывать влияние производительности. Такие возможности приложения, как контроль над времени ожидания ответа сервера, сессиями и ограничение повторных попыток обращения к удаленным службам, помогут обеспечить бесперебойную и устойчивую работу вашего приложения.
Тайм-ауты Timeouts
Допустим ваше приложение отправляет запрос к некоторой удаленной службе, далее основной поток выполнения кода будет приостановлен, пока приложение будет дождаться ответа на отправленный запрос, и лишь после его получения продолжит свою работу. Конечно, если ваше приложение будет слишком долго ожидать ответ от сервера, то это может привести к следующим негативным последствиям: незавершенные запросы к удаленным службам могут автоматически сохраняться и накапливаться в памяти, пострадает отзывчивость интерфейса приложения на действия пользователя, а фоновые задания могут просто зависнуть.
Уточнение для тех, кто пока не сталкивался с этой проблемой. При выполнении синхронного кода каждая операция ожидает окончания предыдущей, то есть код выполняется строго последовательно в одном потоке. Поэтому приложение может зависнуть, если какая-то операция выполняется слишком долго.
Асинхронный код убирает блокирующую операцию из основного потока программы в отдельный, так что она продолжает выполняться, но в другой области памяти, а основной поток продолжает выполнение кода приложения.
По умолчанию requests будет ждать ответа на запрос от сервера до бесконечности, поэтому рекомендуется практически во всех случаях указывать интервал ожидания, чтобы избежать всех, перечисленных выше, негативных последствий. Чтобы установить интервал времени ожидания запроса, используйте именованный параметр timeout. Значение timeout может быть целым числом или числом с плавающей запятой, задающее количество секунд ожидания ответа от сервера до истечения времени ожидания:
>>> requests.get('https://api.github.com', timeout=1)
<Response [200]>
>>> requests.get('https://api.github.com', timeout=3.05)
<Response [200]>
В первом запросе время ожидания истекает через 1 секунду. Во втором — через 3,05 секунды.
Рекомендуется устанавливать
timeoutчуть больше 3 секунд, что определяется величиной кратной длительности по умолчанию окна повторной передачи TCP-пакетов.
В качестве значения параметра timeout можно так же передать кортеж, состоящий из двух значений (connect, read). Его первый элемент connect определяет тайм-аут соединения (время, которое выделяется на установление соединения с удаленным сервером), а второй read — тайм-аут чтения (время, ожидания ответа от сервера, после того, как соединение было установлено):
>>> requests.get('https://api.github.com', timeout=(2, 5))
<Response [200]>
И так если наш запрос устанавливает соединение с сервером в течение 2 секунд и получает от него данные в течение 5 секунд после установления соединения, то содержимое ответа сервера будет возвращено, как это было и раньше. Если же время ожидания истекло, то будет генерироваться исключение типа Timeout:
import requests
from requests.exceptions import Timeout
try:
response = requests.get('https://api.github.com', timeout=1)
except Timeout:
print('The request timed out')
else:
print('The request did not time out')
Таким образом, при необходимости код вашего приложения может перехватить исключение Timeout и обработать его соответствующим образом.
Объект сессии Session
До сих пор мы имели дело с сетевыми интерфейсами запросов и API высокого уровня, такими как методы get() и post(). Эти методы абстрагирует пользователя от того, что происходит “под капотом” библиотеки, когда мы отправляем запросы. Они скрывают детали реализации, такие как управление соединениями, так что нам не приходится об этом задумывать при написании своих приложений.
Одну из основных ролей при абстрагировании пользователя от деталей работы библиотеки, играет класс Session. Если вы хотите осуществлять непосредственный контроль над выполнением запросов, а так же повысить их производительность, то вам необходимо использовать все возможности экземпляров класса Session.
Как мы уже знаем, сессии используются для сохранения некоторого набора настроек от запроса к запросу при обращении к какое-либо удаленной службе. Например, если вы хотите использовать одни и те же данные аутентификации в течение определенного периода времени для отправки нескольких запросов, то можете использовать сессии следующим образом:
import requests
from getpass import getpass
# Используем менеджер контекста так как в случае завершении работы сессии
# все использующиеся ресурсы будут автоматически освобождены
with requests.Session() as session:
session.auth = ('username', getpass())
# вместо requests.get(), используем session.get()
response = session.get('https://api.github.com/user')
# просматриваем содержимое ответа
print(response.headers)
print(response.json())
Каждый раз, когда вы делаете запрос в течение сессии, начиная с того момента, как он был инициализирован с вашими учетными данными аутентификации, эти данные будут сохраняться в течении всей сессии, то есть пока вы используете текущий экземпляр класса Session.
Первичная оптимизация производительности с использованием механизма сессий происходит по причине того, что устанавливается постоянное соединение с сервером. Когда ваше приложение устанавливает соединение с сервером, используя сессии, происходит сохранение текущего соединения в общем пуле сервера. И когда ваше приложение снова захочет подключиться к тому же серверу, то будет повторно использоваться соединение из пула, а не устанавливать новое. Это позволяет сократить время обращения за данными к удаленной службе, что конечно же отражается на производительности вашего приложения.
Количество повторов запроса
В случае если ваш запрос по той или иной причине был неудачен, то вы можете указать приложению повторить тот же запрос заданное число раз. Однако requests не будет это делать для вас по умолчанию. Чтобы использовать эту возможность, необходимо реализовать свой так называемый транспортный адаптер Transport Adapter.
Транспортные адаптеры позволяют вам определить набор конфигураций (настроек) для взаимодействия с конкретной удаленной службой. Допустим, вы хотите, чтобы все запросы к точке доступа https://api.github.com повторялись три раза, прежде чем, наконец, будет генерироваться исключение типа ConnectionError.
import requests
from requests.adapters import HTTPAdapter
from requests.exceptions import ConnectionError
github_adapter = HTTPAdapter(max_retries=3)
session = requests.Session()
# Используйте `github_adapter` для всех запросов к конечным точкам,
# которые начинаются с этого URL
session.mount('https://api.github.com', github_adapter)
try:
session.get('https://api.github.com')
except ConnectionError as ce:
print(ce)
В тех случаях, когда вы используете объект github_adapter класса HTTPAdapter в текущей сессии, в объекте session будут сохраняться заданные вами свойства конфигурации для каждого запроса, отправляемого по адресу https://api.github.com.
Вызов метода монтирования регистрирует новый экземпляр транспортного адаптера для указанного префикса (подстроки адреса). После его подключения любой HTTP-запрос, выполненный с использованием этой сессии, URL-адрес которого начинается с указанного префикса, будет использовать транспортный адаптер github_adapter. При создании нового объекта класса HTTPAdapter именованному параметру max_retries передается значение 3, количество повторов запроса, после чего будет генерироваться исключение ConnectionError. С другими настройками транспортных адаптеров, а так же, для каких целей могут использоваться, вы можете ознакомиться в официальной документации.
Заключение
И так мы прошли долгий путь в изучении возможностей Python библиотеки requests.
Теперь вы умеете:
- Отправлять запросы, используя различные методы протокола HTTP, такие как GET, POST и т.д.
- Управлять отправляемыми запросами с помощью настроек, изменять их заголовки, данные аутентификации, строку запроса и полезное содержимое сообщений.
- Просматривать и обрабатывать данные, которые вы отправляете на сервер, а так же данные, которые сервер посылает вам обратно.
- Использовать процедуру проверки SSL-сертификатов в ходе взаимодействия с удаленным сервером.
- Эффективно использовать различные возможности
requestsдля повышения эффективности и производительности ваших приложений, например,max_retries,timeout,SessionsиTransport Adapters.
Поскольку теперь мы познакомились, с использованием библиотеки requests то, у вас появилась отличная возможность самостоятельно исследовать особенности взаимодействия с различными веб-сервисами и создавать потрясающие приложения, используя полезные данные, которые они предоставляют по вашим запросам.
Готовы начать? Эта статья дает достаточное представление о том, как начать работу с 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) 'x1fx8bx08x00x00x00x00x00x00x03'
Теперь вы можете использовать подобный код как шаблон, чтобы сохранить получаемый поток в файл:
>>> 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,sendnanother,row,to,sendn')} >>> r = requests.post(url, files=files) >>> r.text { ... "files": { "file": "some,data,to,sendnanother,row,to,sendn" }, ... }
В случае, если вы отправляете очень большой файл как 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.
Improve Article
Save Article
Improve Article
Save Article
response.status_code returns a number that indicates the status (200 is OK, 404 is Not Found). Python requests are generally used to fetch the content from a particular resource URI. Whenever we make a request to a specified URI through Python, it returns a response object. Now, this response object would be used to access certain features such as content, headers, etc. This article revolves around how to check the response.status_code out of a response object. To know more about Status codes for HTTP, visit – HTTP status codes | Successful Responses and HTTP status codes | Informational Responses.
How to use response.status_code using Python requests?
To illustrate use of response.status_code, let’s ping api.github.com. To run this script, you need to have Python and requests installed on your PC.
Prerequisites –
- Download and Install Python 3 Latest Version
- How to install requests in Python – For windows, linux, mac
Example code –
Example Implementation –
Save above file as request.py and run using
Python request.py
Output –

Check that and 200 in the output which refer to HttpResponse and Status code respectively.
Advanced Concepts
There are many libraries to make an HTTP request in Python, which are httplib, urllib, httplib2, treq, etc., but requests is the one of the best with cool features. If any attribute of requests shows NULL, check the status code using below attribute.
requests.status_code
If status_code doesn’t lie in range of 200-29. You probably need to check method begin used for making a request + the url you are requesting for resources.