.. 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.
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Making HTTP Requests With Python
The requests
library is the de facto standard for making HTTP requests in Python. It abstracts the complexities of making requests behind a beautiful, simple API so that you can focus on interacting with services and consuming data in your application.
Throughout this article, you’ll see some of the most useful features that requests
has to offer as well as how to customize and optimize those features for different situations you may come across. You’ll also learn how to use requests
in an efficient way as well as how to prevent requests to external services from slowing down your application.
In this tutorial, you’ll learn how to:
- Make requests using the most common HTTP methods
- Customize your requests’ headers and data, using the query string and message body
- Inspect data from your requests and responses
- Make authenticated requests
- Configure your requests to help prevent your application from backing up or slowing down
Though I’ve tried to include as much information as you need to understand the features and examples included in this article, I do assume a very basic general knowledge of HTTP. That said, you still may be able to follow along fine anyway.
Now that that is out of the way, let’s dive in and see how you can use requests
in your application!
Getting Started With requests
Let’s begin by installing the requests
library. To do so, run the following command:
If you prefer to use Pipenv for managing Python packages, you can run the following:
$ pipenv install requests
Once requests
is installed, you can use it in your application. Importing requests
looks like this:
Now that you’re all set up, it’s time to begin your journey through requests
. Your first goal will be learning how to make a GET
request.
The GET Request
HTTP methods such as GET
and POST
, determine which action you’re trying to perform when making an HTTP request. Besides GET
and POST
, there are several other common methods that you’ll use later in this tutorial.
One of the most common HTTP methods is GET
. The GET
method indicates that you’re trying to get or retrieve data from a specified resource. To make a GET
request, invoke requests.get()
.
To test this out, you can make a GET
request to GitHub’s Root REST API by calling get()
with the following URL:
>>>
>>> requests.get('https://api.github.com')
<Response [200]>
Congratulations! You’ve made your first request. Let’s dive a little deeper into the response of that request.
The Response
A Response
is a powerful object for inspecting the results of the request. Let’s make that same request again, but this time store the return value in a variable so that you can get a closer look at its attributes and behaviors:
>>>
>>> response = requests.get('https://api.github.com')
In this example, you’ve captured the return value of get()
, which is an instance of Response
, and stored it in a variable called response
. You can now use response
to see a lot of information about the results of your GET
request.
Status Codes
The first bit of information that you can gather from Response
is the status code. A status code informs you of the status of the request.
For example, a 200 OK
status means that your request was successful, whereas a 404 NOT FOUND
status means that the resource you were looking for was not found. There are many other possible status codes as well to give you specific insights into what happened with your request.
By accessing .status_code
, you can see the status code that the server returned:
>>>
>>> response.status_code
200
.status_code
returned a 200
, which means your request was successful and the server responded with the data you were requesting.
Sometimes, you might want to use this information to make decisions in your code:
if response.status_code == 200:
print('Success!')
elif response.status_code == 404:
print('Not Found.')
With this logic, if the server returns a 200
status code, your program will print Success!
. If the result is a 404
, your program will print Not Found
.
requests
goes one step further in simplifying this process for you. If you use a Response
instance in a conditional expression, it will evaluate to True
if the status code was between 200
and 400
, and False
otherwise.
Therefore, you can simplify the last example by rewriting the if
statement:
if response:
print('Success!')
else:
print('An error has occurred.')
Keep in mind that this method is not verifying that the status code is equal to 200
. The reason for this is that other status codes within the 200
to 400
range, such as 204 NO CONTENT
and 304 NOT MODIFIED
, are also considered successful in the sense that they provide some workable response.
For example, the 204
tells you that the response was successful, but there’s no content to return in the message body.
So, make sure you use this convenient shorthand only if you want to know if the request was generally successful and then, if necessary, handle the response appropriately based on the status code.
Let’s say you don’t want to check the response’s status code in an if
statement. Instead, you want to raise an exception if the request was unsuccessful. You can do this using .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)
# If the response was successful, no Exception will be raised
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!')
If you invoke .raise_for_status()
, an HTTPError
will be raised for certain status codes. If the status code indicates a successful request, the program will proceed without that exception being raised.
Now, you know a lot about how to deal with the status code of the response you got back from the server. However, when you make a GET
request, you rarely only care about the status code of the response. Usually, you want to see more. Next, you’ll see how to view the actual data that the server sent back in the body of the response.
Content
The response of a GET
request often has some valuable information, known as a payload, in the message body. Using the attributes and methods of Response
, you can view the payload in a variety of different formats.
To see the response’s content in bytes
, you use .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}"}'
While .content
gives you access to the raw bytes of the response payload, you will often want to convert them into a string using a character encoding such as UTF-8. response
will do that for you when you access .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}"}'
Because the decoding of bytes
to a str
requires an encoding scheme, requests
will try to guess the encoding based on the response’s headers if you do not specify one. You can provide an explicit encoding by setting .encoding
before accessing .text
:
>>>
>>> response.encoding = 'utf-8' # Optional: requests infers this internally
>>> 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}"}'
If you take a look at the response, you’ll see that it is actually serialized JSON content. To get a dictionary, you could take the str
you retrieved from .text
and deserialize it using json.loads()
. However, a simpler way to accomplish this task is to use .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}'}
The type
of the return value of .json()
is a dictionary, so you can access values in the object by key.
You can do a lot with status codes and message bodies. But, if you need more information, like metadata about the response itself, you’ll need to look at the response’s headers.
Query String Parameters
One common way to customize a GET
request is to pass values through query string parameters in the URL. To do this using get()
, you pass data to params
. For example, you can use GitHub’s Search API to look for the requests
library:
import requests
# Search GitHub's repositories for requests
response = requests.get(
'https://api.github.com/search/repositories',
params={'q': 'requests+language:python'},
)
# Inspect some attributes of the `requests` repository
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+
By passing the dictionary {'q': 'requests+language:python'}
to the params
parameter of .get()
, you are able to modify the results that come back from the Search API.
You can pass params
to get()
in the form of a dictionary, as you have just done, or as a list of tuples:
>>>
>>> requests.get(
... 'https://api.github.com/search/repositories',
... params=[('q', 'requests+language:python')],
... )
<Response [200]>
You can even pass the values as bytes
:
>>>
>>> requests.get(
... 'https://api.github.com/search/repositories',
... params=b'q=requests+language:python',
... )
<Response [200]>
Query strings are useful for parameterizing GET
requests. You can also customize your requests by adding or modifying the headers you send.
Other HTTP Methods
Aside from GET
, other popular HTTP methods include POST
, PUT
, DELETE
, HEAD
, PATCH
, and OPTIONS
. requests
provides a method, with a similar signature to get()
, for each of these HTTP methods:
>>>
>>> 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')
Each function call makes a request to the httpbin
service using the corresponding HTTP method. For each method, you can inspect their responses in the same way you did before:
>>>
>>> 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']
{}
Headers, response bodies, status codes, and more are returned in the Response
for each method. Next you’ll take a closer look at the POST
, PUT
, and PATCH
methods and learn how they differ from the other request types.
The Message Body
According to the HTTP specification, POST
, PUT
, and the less common PATCH
requests pass their data through the message body rather than through parameters in the query string. Using requests
, you’ll pass the payload to the corresponding function’s data
parameter.
data
takes a dictionary, a list of tuples, bytes, or a file-like object. You’ll want to adapt the data you send in the body of your request to the specific needs of the service you’re interacting with.
For example, if your request’s content type is application/x-www-form-urlencoded
, you can send the form data as a dictionary:
>>>
>>> requests.post('https://httpbin.org/post', data={'key':'value'})
<Response [200]>
You can also send that same data as a list of tuples:
>>>
>>> requests.post('https://httpbin.org/post', data=[('key', 'value')])
<Response [200]>
If, however, you need to send JSON data, you can use the json
parameter. When you pass JSON data via json
, requests
will serialize your data and add the correct Content-Type
header for you.
httpbin.org is a great resource created by the author of requests
, Kenneth Reitz. It’s a service that accepts test requests and responds with data about the requests. For instance, you can use it to inspect a basic POST
request:
>>>
>>> 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'
You can see from the response that the server received your request data and headers as you sent them. requests
also provides this information to you in the form of a PreparedRequest
.
Inspecting Your Request
When you make a request, the requests
library prepares the request before actually sending it to the destination server. Request preparation includes things like validating headers and serializing JSON content.
You can view the PreparedRequest
by accessing .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"}'
Inspecting the PreparedRequest
gives you access to all kinds of information about the request being made such as payload, URL, headers, authentication, and more.
So far, you’ve made a lot of different kinds of requests, but they’ve all had one thing in common: they’re unauthenticated requests to public APIs. Many services you may come across will want you to authenticate in some way.
Authentication
Authentication helps a service understand who you are. Typically, you provide your credentials to a server by passing data through the Authorization
header or a custom header defined by the service. All the request functions you’ve seen to this point provide a parameter called auth
, which allows you to pass your credentials.
One example of an API that requires authentication is GitHub’s Authenticated User API. This endpoint provides information about the authenticated user’s profile. To make a request to the Authenticated User API, you can pass your GitHub username and password in a tuple to get()
:
>>>
>>> from getpass import getpass
>>> requests.get('https://api.github.com/user', auth=('username', getpass()))
<Response [200]>
The request succeeded if the credentials you passed in the tuple to auth
are valid. If you try to make this request with no credentials, you’ll see that the status code is 401 Unauthorized
:
>>>
>>> requests.get('https://api.github.com/user')
<Response [401]>
When you pass your username and password in a tuple to the auth
parameter, requests
is applying the credentials using HTTP’s Basic access authentication scheme under the hood.
Therefore, you could make the same request by passing explicit Basic authentication credentials using HTTPBasicAuth
:
>>>
>>> from requests.auth import HTTPBasicAuth
>>> from getpass import getpass
>>> requests.get(
... 'https://api.github.com/user',
... auth=HTTPBasicAuth('username', getpass())
... )
<Response [200]>
Though you don’t need to be explicit for Basic authentication, you may want to authenticate using another method. requests
provides other methods of authentication out of the box such as HTTPDigestAuth
and HTTPProxyAuth
.
You can even supply your own authentication mechanism. To do so, you must first create a subclass of AuthBase
. Then, you implement __call__()
:
import requests
from requests.auth import AuthBase
class TokenAuth(AuthBase):
"""Implements a custom authentication scheme."""
def __init__(self, token):
self.token = token
def __call__(self, r):
"""Attach an API token to a custom auth header."""
r.headers['X-TokenAuth'] = f'{self.token}' # Python 3.6+
return r
requests.get('https://httpbin.org/get', auth=TokenAuth('12345abcde-token'))
Here, your custom TokenAuth
mechanism receives a token, then includes that token in the X-TokenAuth
header of your request.
Bad authentication mechanisms can lead to security vulnerabilities, so unless a service requires a custom authentication mechanism for some reason, you’ll always want to use a tried-and-true auth scheme like Basic or OAuth.
While you’re thinking about security, let’s consider dealing with SSL Certificates using requests
.
SSL Certificate Verification
Any time the data you are trying to send or receive is sensitive, security is important. The way that you communicate with secure sites over HTTP is by establishing an encrypted connection using SSL, which means that verifying the target server’s SSL Certificate is critical.
The good news is that requests
does this for you by default. However, there are some cases where you might want to change this behavior.
If you want to disable SSL Certificate verification, you pass False
to the verify
parameter of the request function:
>>>
>>> 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
even warns you when you’re making an insecure request to help you keep your data safe!
Performance
When using requests
, especially in a production application environment, it’s important to consider performance implications. Features like timeout control, sessions, and retry limits can help you keep your application running smoothly.
Timeouts
When you make an inline request to an external service, your system will need to wait upon the response before moving on. If your application waits too long for that response, requests to your service could back up, your user experience could suffer, or your background jobs could hang.
By default, requests
will wait indefinitely on the response, so you should almost always specify a timeout duration to prevent these things from happening. To set the request’s timeout, use the timeout
parameter. timeout
can be an integer or float representing the number of seconds to wait on a response before timing out:
>>>
>>> requests.get('https://api.github.com', timeout=1)
<Response [200]>
>>> requests.get('https://api.github.com', timeout=3.05)
<Response [200]>
In the first request, the request will timeout after 1 second. In the second request, the request will timeout after 3.05 seconds.
You can also pass a tuple to timeout
with the first element being a connect timeout (the time it allows for the client to establish a connection to the server), and the second being a read timeout (the time it will wait on a response once your client has established a connection):
>>>
>>> requests.get('https://api.github.com', timeout=(2, 5))
<Response [200]>
If the request establishes a connection within 2 seconds and receives data within 5 seconds of the connection being established, then the response will be returned as it was before. If the request times out, then the function will raise a Timeout
exception:
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')
Your program can catch the Timeout
exception and respond accordingly.
The Session Object
Until now, you’ve been dealing with high level requests
APIs such as get()
and post()
. These functions are abstractions of what’s going on when you make your requests. They hide implementation details such as how connections are managed so that you don’t have to worry about them.
Underneath those abstractions is a class called Session
. If you need to fine-tune your control over how requests are being made or improve the performance of your requests, you may need to use a Session
instance directly.
Sessions are used to persist parameters across requests. For example, if you want to use the same authentication across multiple requests, you could use a session:
import requests
from getpass import getpass
# By using a context manager, you can ensure the resources used by
# the session will be released after use
with requests.Session() as session:
session.auth = ('username', getpass())
# Instead of requests.get(), you'll use session.get()
response = session.get('https://api.github.com/user')
# You can inspect the response just like you did before
print(response.headers)
print(response.json())
Each time you make a request with session
, once it has been initialized with authentication credentials, the credentials will be persisted.
The primary performance optimization of sessions comes in the form of persistent connections. When your app makes a connection to a server using a Session
, it keeps that connection around in a connection pool. When your app wants to connect to the same server again, it will reuse a connection from the pool rather than establishing a new one.
Max Retries
When a request fails, you may want your application to retry the same request. However, requests
will not do this for you by default. To apply this functionality, you need to implement a custom Transport Adapter.
Transport Adapters let you define a set of configurations per service you’re interacting with. For example, let’s say you want all requests to https://api.github.com
to retry three times before finally raising a ConnectionError
. You would build a Transport Adapter, set its max_retries
parameter, and mount it to an existing Session
:
import requests
from requests.adapters import HTTPAdapter
from requests.exceptions import ConnectionError
github_adapter = HTTPAdapter(max_retries=3)
session = requests.Session()
# Use `github_adapter` for all requests to endpoints that start with this URL
session.mount('https://api.github.com', github_adapter)
try:
session.get('https://api.github.com')
except ConnectionError as ce:
print(ce)
When you mount the HTTPAdapter
, github_adapter
, to session
, session
will adhere to its configuration for each request to https://api.github.com.
Timeouts, Transport Adapters, and sessions are for keeping your code efficient and your application resilient.
Conclusion
You’ve come a long way in learning about Python’s powerful requests
library.
You’re now able to:
- Make requests using a variety of different HTTP methods such as
GET
,POST
, andPUT
- Customize your requests by modifying headers, authentication, query strings, and message bodies
- Inspect the data you send to the server and the data the server sends back to you
- Work with SSL Certificate verification
- Use
requests
effectively usingmax_retries
,timeout
, Sessions, and Transport Adapters
Because you learned how to use requests
, you’re equipped to explore the wide world of web services and build awesome applications using the fascinating data they provide.
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Making HTTP Requests With Python
Перевод статьи 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
является стандартным инструментом для составления HTTP-запросов в Python. Простой и аккуратный API значительно облегчает трудоемкий процесс создания запросов. Таким образом, можно сосредоточиться на взаимодействии со службами и использовании данных в приложении.
Содержание статьи
- Python установка библиотеки requests
- Python библиотека Requests метод GET
- Объект Response получение ответа на запрос в Python
- HTTP коды состояний
- Получить содержимое страницы в Requests
- HTTP заголовки в Requests
- Python Requests параметры запроса
- Настройка HTTP заголовка запроса (headers)
- Примеры HTTP методов в Requests
- Python Requests тело сообщения
- Python Requests анализ запроса
- Python Requests аутентификация HTTP AUTH
- Python Requests проверка SSL сертификата
- Python Requests производительность приложений
- Объект Session в Requests
- HTTPAdapter — Максимальное количество повторов запроса в Requests
В данной статье представлены наиболее полезные особенности requests. Показано, как изменить и приспособить requests к различным ситуациям, с которыми программисты сталкиваются чаще всего. Здесь также даются советы по эффективному использованию requests и предотвращению влияния сторонних служб, которые могут сильно замедлить работу используемого приложения. Мы использовали библиотек requests в уроке по парсингу html через библиотеку BeautifulSoup.
Ключевые аспекты инструкции:
- Создание запросов при помощи самых популярных HTTP методов;
- Редактирование заголовков запросов и данных при помощи строки запроса и содержимого сообщения;
- Анализ данных запросов и откликов;
- Создание авторизированных запросов;
- Настройка запросов для предотвращения сбоев и замедления работы приложения.
В статье собран оптимальный набор информации, необходимый для понимания данных примеров и особенностей их использования. Информация представлена в доступной в форме. Тем не менее, стоит иметь в виду, что для оптимального разбора инструкции потребуются хотя бы базовые знания HTTP.
Далее будут показаны наиболее эффективные методы использования requests
в разрабатываемом приложении.
Python установка библиотеки requests
Для начала работы потребуется установить библиотеку requests
. Для этого используется следующая команда.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Telegram Чат & Канал
Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Тем, кто для работы с пакетами Python, использует виртуальную среду Pipenv, необходимо использовать немного другую команду.
$ pipenv install requests |
Сразу после установки requests
можно полноценно использовать в приложении. Импорт requests
производится следующим образом.
Таким образом, все подготовительные этапы для последующего использования requests
завершены. Начинать изучение requests лучше всего с того, как сделать запрос GET
.
Python библиотека Requests метод GET
Такие HTTP методы, как GET
и POST
, определяют, какие действия будут выполнены при создании HTTP запроса. Помимо GET
и POST
для этой задачи могут быть использованы некоторые другие методы. Далее они также будут описаны в руководстве.
GET
является одним из самых популярных HTTP методов. Метод GET
указывает на то, что происходит попытка извлечь данные из определенного ресурса. Для того, чтобы выполнить запрос GET
, используется requests.get()
.
Для проверки работы команды будет выполнен запрос GET
в отношении Root REST API на GitHub. Для указанного ниже URL вызывается метод get()
.
requests.get(‘https://api.github.com’) <Response [200]> |
Если никакие python ошибки не возникло, вас можно поздравить – первый запрос успешно выполнен. Далее будет рассмотрен ответ на данный запрос, который можно получить при помощи объекта Response.
Объект Response получение ответа на запрос в Python
Response
представляет собой довольно мощный объект для анализа результатов запроса. В качестве примера будет использован предыдущий запрос, только на этот раз результат будет представлен в виде переменной. Таким образом, получится лучше изучить его атрибуты и особенности использования.
response = requests.get(‘https://api.github.com’) |
В данном примере при помощи get()
захватывается определенное значение, что является частью объекта Response
, и помещается в переменную под названием response
. Теперь можно использовать переменную response
для того, чтобы изучить данные, которые были получены в результате запроса GET
.
HTTP коды состояний
Самыми первыми данными, которые будут получены через Response
, будут коды состояния. Коды состояния сообщают о статусе запроса.
Например, статус 200 OK
значит, что запрос успешно выполнен. А вот статус 404 NOT FOUND
говорит о том, что запрашиваемый ресурс не был найден. Существует множество других статусных кодов, которые могут сообщить важную информацию, связанную с запросом.
Используя .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
в условных конструкциях, то при получении кода состояния в промежутке от 200
до 400
, будет выведено значение True
. В противном случае отобразится значение False
.
Последний пример можно упростить при помощи использования оператора if
.
if response: print(‘Success!’) else: print(‘An error has occurred.’) |
Стоит иметь в виду, что данный способ не проверяет, имеет ли статусный код точное значение 200
. Причина заключается в том, что другие коды в промежутке от 200
до 400
, например, 204 NO CONTENT
и 304 NOT MODIFIED
, также считаются успешными в случае, если они могут предоставить действительный ответ.
К примеру, код состояния 204
говорит о том, что ответ успешно получен, однако в полученном объекте нет содержимого. Можно сказать, что для оптимально эффективного использования способа необходимо убедиться, что начальный запрос был успешно выполнен. Требуется изучить код состояния и в случае необходимости произвести необходимые поправки, которые будут зависеть от значения полученного кода.
Допустим, если при использовании оператора if
вы не хотите проверять код состояния, можно расширить диапазон исключений для неудачных результатов запроса. Это можно сделать при помощи использования .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) # если ответ успешен, исключения задействованы не будут 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
. Когда код состояния показывает, что запрос успешно выполнен, программа продолжает работу без применения политики исключений.
На заметку. Для более продуктивной работы в Python 3.6 будет не лишним изучить f-строки. Не стоит пренебрегать ими, так как это отличный способ упростить форматирование строк.
Анализ способов использования кодов состояния, полученных с сервера, является неплохим стартом для изучения requests. Тем не менее, при создании запроса GET, значение кода состояния является не самой важной информацией, которую хочет получить программист. Обычно запрос производится для извлечения более содержательной информации. В дальнейшем будет показано, как добраться до актуальных данных, которые сервер высылает отправителю в ответ на запрос.
Зачастую ответ на запрос GET
содержит весьма ценную информацию. Она находится в теле сообщения и называется пейлоад (payload). Используя атрибуты и методы библиотеки 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}»}’ |
Использование .content
обеспечивает доступ к чистым байтам ответного пейлоада, то есть к любым данным в теле запроса. Однако, зачастую требуется конвертировать полученную информацию в строку в кодировке UTF-8. 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}»}’ |
Декодирование байтов в строку требует наличия определенной модели кодировки. По умолчанию requests
попытается узнать текущую кодировку, ориентируясь по заголовкам HTTP. Указать необходимую кодировку можно при помощи добавления .encoding
перед .text
.
>>> response.encoding = ‘utf-8’ # Optional: requests infers this internally >>> 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 контентом. Воспользовавшись словарем, можно взять полученные из .text
строки str
и провести с ними обратную сериализацию при помощи использования json.loads(). Есть и более простой способ, который требует применения .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}’} |
Тип полученного значения из .json()
, является словарем. Это значит, что доступ к его содержимому можно получить по ключу.
Коды состояния и тело сообщения предоставляют огромный диапазон возможностей. Однако, для их оптимального использования требуется изучить метаданные и заголовки HTTP.
HTTP заголовки ответов на запрос могут предоставить определенную полезную информацию. Это может быть тип содержимого ответного пейлоада, а также ограничение по времени для кеширования ответа. Для просмотра HTTP заголовков загляните в атрибут .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’} |
.headers
возвращает словарь, что позволяет получить доступ к значению заголовка HTTP по ключу. Например, для просмотра типа содержимого ответного пейлоада, требуется использовать 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
.
Python Requests параметры запроса
Наиболее простым способом настроить запрос GET
является передача значений через параметры строки запроса в URL. При использовании метода get()
, данные передаются в params
. Например, для того, чтобы посмотреть на библиотеку requests
можно использовать Search API на GitHub.
import requests # Поиск местонахождения для запросов на GitHub response = requests.get( ‘https://api.github.com/search/repositories’, params={‘q’: ‘requests+language:python’}, ) # Анализ некоторых атрибутов местонахождения запросов 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
.
Можно передать параметры в get()
в форме словаря, как было показано выше. Также можно использовать список кортежей.
>>> requests.get( ... ‘https://api.github.com/search/repositories’, ... params=[(‘q’, ‘requests+language:python’)], ... ) <Response [200]> |
Также можно передать значение в байтах.
>>> requests.get( ... ‘https://api.github.com/search/repositories’, ... params=b‘q=requests+language:python’, ... ) <Response [200]> |
Строки запроса полезны для уточнения параметров в запросах GET
. Также можно настроить запросы при помощи добавления или изменения заголовков отправленных сообщений.
Для изменения HTTP заголовка требуется передать словарь данного HTTP заголовка в get()
при помощи использования параметра headers
. Например, можно изменить предыдущий поисковой запрос, подсветив совпадения в результате. Для этого в заголовке Accept
медиа тип уточняется при помощи text-match
.
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.
Примеры HTTP методов в Requests
Помимо GET
, большой популярностью пользуются такие методы, как POST
, PUT
, DELETE
, HEAD
, PATCH
и OPTIONS
. Для каждого из этих методов существует своя сигнатура, которая очень похожа на метод get()
.
>>> 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
могут быть возвращены заголовки, тело запроса, коды состояния и многие другие аспекты. Методы POST
, PUT
и PATCH
в дальнейшем будут описаны более подробно.
Python Requests тело сообщения
В соответствии со спецификацией HTTP запросы POST
, PUT
и PATCH
передают информацию через тело сообщения, а не через параметры строки запроса. Используя requests
, можно передать данные в параметр data
.
В свою очередь data
использует словарь, список кортежей, байтов или объект файла. Это особенно важно, так как может возникнуть необходимость адаптации отправляемых с запросом данных в соответствии с определенными параметрами сервера.
К примеру, если тип содержимого запроса application/x-www-form-urlencoded
, можно отправить данные формы в виде словаря.
>>> 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 через 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’ |
Здесь видно, что сервер получил данные и HTTP заголовки, отправленные вместе с запросом. requests
также предоставляет информацию в форме PreparedRequest
.
Python Requests анализ запроса
При составлении запроса стоит иметь в виду, что перед его фактической отправкой на целевой сервер библиотека requests
выполняет определенную подготовку. Подготовка запроса включает в себя такие вещи, как проверка заголовков и сериализация содержимого JSON.
Если открыть .request
, можно просмотреть PreparedRequest
.
>>> 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, заголовки, аутентификация и многое другое.
У всех описанных ранее типов запросов была одна общая черта – они представляли собой неаутентифицированные запросы к публичным API. Однако, подобающее большинство служб, с которыми может столкнуться пользователь, запрашивают аутентификацию.
Python Requests аутентификация HTTP AUTH
Аутентификация помогает сервису понять, кто вы. Как правило, вы предоставляете свои учетные данные на сервер, передавая данные через заголовок Authorization
или пользовательский заголовок, определенной службы. Все функции запроса, которые вы видели до этого момента, предоставляют параметр с именем auth
, который позволяет вам передавать свои учетные данные.
Одним из примеров API, который требует аутентификации, является 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
, вы используете учетные данные при помощи базовой схемы аутентификации HTTP.
Таким образом, вы можете создать тот же запрос, передав подробные учетные данные базовой аутентификации, используя HTTPBasicAuth
.
>>> from requests.auth import HTTPBasicAuth >>> from getpass import getpass >>> requests.get( ... ‘https://api.github.com/user’, ... auth=HTTPBasicAuth(‘username’, getpass()) ... ) <Response [200]> |
Хотя вам не нужно явно указывать обычную аутентификацию, может потребоваться аутентификация с использованием другого метода. requests
предоставляет другие методы аутентификации, например, HTTPDigestAuth
и HTTPProxyAuth
.
Вы даже можете предоставить свой собственный механизм аутентификации. Для этого необходимо сначала создать подкласс AuthBase. Затем происходит имплементация __call__()
.
import requests from requests.auth import AuthBase class TokenAuth(AuthBase): «»»Implements a custom authentication scheme.»»» def __init__(self, token): self.token = token def __call__(self, r): «»»Attach an API token to a custom auth header.»»» 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 или OAuth.
Пока вы думаете о безопасности, давайте рассмотрим использование requests
в SSL сертификатах.
Python Requests проверка SSL сертификата
Всякий раз, когда данные, которые вы пытаетесь отправить или получить, являются конфиденциальными, безопасность важна. Вы общаетесь с защищенными сайтами через HTTP, устанавливая зашифрованное соединение с использованием SSL, что означает, что проверка SSL сертификата целевого сервера имеет решающее значение.
Хорошей новостью является то, что requests
по умолчанию все делает сам. Однако в некоторых случаях необходимо внести определенные поправки.
Если требуется отключить проверку SSL-сертификата, параметру verify
функции запроса можно присвоить значение False
.
>>> 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
, чтобы обеспечить максимальную безопасность ваших соединений.
Python Requests производительность приложений
При использовании requests
, особенно в среде приложений, важно учитывать влияние на производительность. Такие функции, как контроль таймаута, сеансы и ограничения повторных попыток, могут помочь обеспечить бесперебойную работу приложения.
Таймауты
Когда вы отправляете встроенный запрос во внешнюю службу, вашей системе нужно будет дождаться ответа, прежде чем двигаться дальше. Если ваше приложение слишком долго ожидает ответа, запросы к службе могут быть сохранены, пользовательский интерфейс может пострадать или фоновые задания могут зависнуть.
По умолчанию в 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 секунды.
Вы также можете передать кортеж. Это – таймаут соединения (время, за которое клиент может установить соединение с сервером), а второй – таймаут чтения (время ожидания ответа, как только ваш клиент установил соединение):
>>> 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 в Requests
До сих пор вы имели дело с requests
API высокого уровня, такими как get()
и post()
. Эти функции являются абстракцией того, что происходит, когда вы делаете свои запросы. Они скрывают детали реализации, такие как управление соединениями, так что вам не нужно о них беспокоиться.
Под этими абстракциями находится класс под названием Session
. Если вам необходимо настроить контроль над выполнением запросов или повысить производительность ваших запросов, вам может потребоваться использовать Session
напрямую.
Сессии используются для сохранения параметров в запросах.
Например, если вы хотите использовать одну и ту же аутентификацию для нескольких запросов, вы можете использовать сеанс:
import requests from getpass import getpass # используя менеджер контента, можно убедиться, что ресурсы, применимые # во время сессии будут свободны после использования with requests.Session() as session: session.auth = (‘username’, getpass()) # Instead of requests.get(), you’ll use session.get() response = session.get(‘https://api.github.com/user’) # здесь можно изучить ответ print(response.headers) print(response.json()) |
Каждый раз, когда вы делаете запрос session
, после того как он был инициализирован с учетными данными аутентификации, учетные данные будут сохраняться.
Первичная оптимизация производительности сеансов происходит в форме постоянных соединений. Когда ваше приложение устанавливает соединение с сервером с помощью Session
, оно сохраняет это соединение в пуле соединений. Когда ваше приложение снова хочет подключиться к тому же серверу, оно будет использовать соединение из пула, а не устанавливать новое.
HTTPAdapter — Максимальное количество повторов запроса в Requests
В случае сбоя запроса возникает необходимость сделать повторный запрос. Однако requests
не будет делать это самостоятельно. Для применения функции повторного запроса требуется реализовать собственный транспортный адаптер.
Транспортные адаптеры позволяют определить набор конфигураций для каждой службы, с которой вы взаимодействуете. Предположим, вы хотите, чтобы все запросы к https://api.github.com
были повторены три раза, прежде чем, наконец, появится ConnectionError
. Для этого нужно построить транспортный адаптер, установить его параметр max_retries
и подключить его к существующему объекту Session
.
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) |
При установке HTTPAdapter
, github_adapter
к session
, session
будет придерживаться своей конфигурации для каждого запроса к https://api.github.com
.
Таймауты, транспортные адаптеры и сессии предназначены для обеспечения эффективности используемого кода и стабильности приложения.
Заключение
Изучение библиотеки Python requests является очень трудоемким процессом.
После разбора данных в статье примеров можно научиться тому, как:
- Создавать запросы, используя различные методы HTTP –
GET
,POST
иPUT
; - Настраивать свои запросы, изменив заголовки, аутентификацию, строки запросов и тела сообщений;
- Проверять данные, которые были отправлены на сервер, а также те данные, которые сервер отправил обратно;
- Работать с проверкой SSL сертификата;
- Эффективно использовать
requests
,max_retries
,timeout
,Sessions
и транспортные адаптеры.
Грамотное использование requests позволит наиболее эффективно настроить разрабатываемые приложения, исследуя широкий спектр веб-сервисов и данных, опубликованных на них.
- Данная статья является переводом статьи: Python’s Requests Library (Guide)
- Изображение статьи принадлежит сайту © RealPython
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»
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.
С помощью модуля реквест можно выполнить множество функций: от авторизации на сайте до скачивания нескольких файлов одновременно.
Содержание
- Quickstart¶
- Make a Request¶
- Passing Parameters In URLs¶
- Response Content¶
- Binary Response Content¶
- JSON Response Content¶
- Raw Response Content¶
- Custom Headers¶
- More complicated POST requests¶
- POST a Multipart-Encoded File¶
- Response Status Codes¶
- Response Headers¶
- Cookies¶
- Redirection and History¶
- Timeouts¶
- Errors and Exceptions¶
- Developer Interface¶
- Main Interface¶
- Exceptions¶
- Request Sessions¶
- Lower-Level Classes¶
- Lower-Lower-Level Classes¶
- Authentication¶
- Encodings¶
- Cookies¶
- Status Code Lookup¶
- Migrating to 1.x¶
- API Changes¶
- Licensing¶
- Migrating to 2.x¶
- API Changes¶
- Behavioural Changes¶
- Stay Informed
Quickstart¶
Eager to get started? This page gives a good introduction in how to get started with Requests.
First, make sure that:
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:
Now, let’s try to get a webpage. For this example, let’s get GitHub’s public timeline:
Now, we have a 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:
Nice, right? What about the other HTTP request types: PUT, DELETE, HEAD and OPTIONS? These are all just as simple:
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:
You can see that the URL has been correctly encoded by printing the URL:
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:
Response Content¶
We can read the content of the server’s response. Consider the GitHub timeline again:
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:
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:
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:
JSON Response Content¶
There’s also a builtin JSON decoder, in case you’re dealing with JSON data:
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:
In general, however, you should use a pattern like this to save what is being streamed to a file:
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.
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 .
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:
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 the auth= parameter. Requests will search for the netrc file at
/_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:
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:
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:
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:
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:
You can set the filename, content_type and headers explicitly:
If you want, you can send strings to be received as files:
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 advanced section.
It is strongly recommended that you open files in binary mode . 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:
Requests also comes with a built-in status code lookup object for easy reference:
If we made a bad request (a 4XX client error or 5XX server error response), we can raise it with Response.raise_for_status() :
But, since our status_code for r was 200 , when we call raise_for_status() we get:
We can view the server’s response headers using a Python dictionary:
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:
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:
To send your own cookies to the server, you can use the cookies parameter:
Cookies are returned in a 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:
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 Response.history list contains the 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:
If you’re using GET, OPTIONS, POST, PUT, PATCH or DELETE, you can disable redirection handling with the allow_redirects parameter:
If you’re using HEAD, you can enable redirection as well:
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:
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 ConnectionError exception.
Response.raise_for_status() will raise an HTTPError if the HTTP request returned an unsuccessful status code.
If a request times out, a Timeout exception is raised.
If a request exceeds the configured number of maximum redirections, a TooManyRedirects exception is raised.
All exceptions that Requests explicitly raises inherit from requests.exceptions.RequestException .
Ready for more? Check out the 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 is an elegant and simple HTTP library for Python, built for human beings. You are currently looking at the documentation of the development release.
Источник
Developer Interface¶
This part of the documentation covers all the interfaces of Requests. For parts where Requests depends on external libraries, we document the most important right here and provide links to the canonical documentation.
Main Interface¶
All of Requests’ functionality can be accessed by these 7 methods. They all return an instance of the Response object.
Constructs and sends a Request .
Sends a HEAD request.
Parameters: |
|
---|---|
Returns: |
Parameters: |
|
---|---|
Returns: |
requests. get ( url, params=None, **kwargs ) [source] ¶
Sends a GET request.
Parameters: |
|
---|---|
Returns: |
requests. post ( url, data=None, json=None, **kwargs ) [source] ¶
Sends a POST request.
Parameters: |
|
---|---|
Returns: |
requests. put ( url, data=None, **kwargs ) [source] ¶
Sends a PUT request.
Parameters: |
|
---|---|
Returns: |
requests. patch ( url, data=None, **kwargs ) [source] ¶
Sends a PATCH request.
Parameters: |
|
---|---|
Returns: |
requests. delete ( url, **kwargs ) [source] ¶
Sends a DELETE request.
Exceptions¶
There was an ambiguous exception that occurred while handling your request.
exception requests. ConnectionError ( *args, **kwargs ) [source] ¶
A Connection error occurred.
exception requests. HTTPError ( *args, **kwargs ) [source] ¶
An HTTP error occurred.
exception requests. URLRequired ( *args, **kwargs ) [source] ¶
A valid URL is required to make a request.
exception requests. TooManyRedirects ( *args, **kwargs ) [source] ¶
Too many redirects.
exception requests. ConnectTimeout ( *args, **kwargs ) [source] ¶
The request timed out while trying to connect to the remote server.
Requests that produced this error are safe to retry.
exception requests. ReadTimeout ( *args, **kwargs ) [source] ¶
The server did not send any data in the allotted amount of time.
The request timed out.
Catching this error will catch both ConnectTimeout and ReadTimeout errors.
Request Sessions¶
A Requests session.
Provides cookie persistence, connection-pooling, and configuration.
Or as a context manager:
Default Authentication tuple or object to attach to Request .
SSL client certificate default, if String, path to ssl client cert file (.pem). If Tuple, (‘cert’, ‘key’) pair.
Closes all adapters and as such the session
A CookieJar containing all currently outstanding cookies set on this session. By default it is a RequestsCookieJar , but may be any other cookielib.CookieJar compatible object.
Sends a DELETE request. Returns Response object.
Parameters: |
|
---|---|
Returns: |
Parameters: |
|
---|---|
Return type: |
get ( url, **kwargs ) [source] ¶
Sends a GET request. Returns Response object.
Parameters: |
|
---|---|
Return type: |
get_adapter ( url ) [source] ¶
Returns the appropriate connection adapter for the given URL.
Return type: | requests.adapters.BaseAdapter |
---|
get_redirect_target ( resp ) ¶
Receives a Response. Returns a redirect URI or None
Sends a HEAD request. Returns Response object.
Parameters: |
|
---|---|
Return type: |
headers = None¶
A case-insensitive dictionary of headers to be sent on each Request sent from this Session .
Maximum number of redirects allowed. If the request exceeds this limit, a TooManyRedirects exception is raised. This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is 30.
Check the environment and merge it with some settings.
Return type: | dict |
---|
mount ( prefix, adapter ) [source] ¶
Registers a connection adapter to a prefix.
Adapters are sorted in descending order by prefix length.
Sends a OPTIONS request. Returns Response object.
Parameters: |
|
---|---|
Return type: |
params = None¶
Dictionary of querystring data to attach to each Request . The dictionary values may be lists for representing multivalued query parameters.
Sends a PATCH request. Returns Response object.
Parameters: |
|
---|---|
Return type: |
post ( url, data=None, json=None, **kwargs ) [source] ¶
Sends a POST request. Returns Response object.
Parameters: |
|
---|---|
Return type: |
prepare_request ( request ) [source] ¶
Constructs a PreparedRequest for transmission and returns it. The PreparedRequest has settings merged from the Request instance and those of the Session .
Parameters: | request – Request instance to prepare with this session’s settings. |
---|---|
Return type: | requests.PreparedRequest |
proxies = None¶
Dictionary mapping protocol or protocol and host to the URL of the proxy (e.g. <‘http’: ‘foo.bar:3128’, ‘http://host.name’: ‘foo.bar:4012’>) to be used on each Request .
Sends a PUT request. Returns Response object.
Parameters: |
|
---|---|
Return type: |
rebuild_auth ( prepared_request, response ) ¶
When being redirected we may want to strip authentication from the request to avoid leaking credentials. This method intelligently removes and reapplies authentication where possible to avoid credential loss.
rebuild_method ( prepared_request, response ) ¶
When being redirected we may want to change the method of the request based on certain specs or browser behavior.
rebuild_proxies ( prepared_request, proxies ) ¶
This method re-evaluates the proxy configuration by considering the environment variables. If we are redirected to a URL covered by NO_PROXY, we strip the proxy configuration. Otherwise, we set missing proxy keys for this URL (in case they were stripped by a previous redirect).
This method also replaces the Proxy-Authorization header where necessary.
Return type: | dict |
---|
request ( method, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=True, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None ) [source] ¶
Constructs a Request , prepares it and sends it. Returns Response object.
Parameters: |
|
---|---|
Return type: |
resolve_redirects ( resp, req, stream=False, timeout=None, verify=True, cert=None, proxies=None, yield_requests=False, **adapter_kwargs ) ¶
Receives a Response. Returns a generator of Responses or Requests.
Send a given PreparedRequest.
Return type: | requests.Response |
---|
should_strip_auth ( old_url, new_url ) ¶
Decide whether Authorization header should be removed when redirecting
Stream response content default.
Trust environment settings for proxy configuration, default authentication and similar.
SSL Verification default.
Lower-Level Classes¶
A user-created Request object.
Used to prepare a PreparedRequest , which is sent to the server.
Parameters: |
|
---|
Deregister a previously registered hook. Returns True if the hook existed, False if not.
Constructs a PreparedRequest for transmission and returns it.
Properly register a hook.
class requests. Response [source] ¶
The Response object, which contains a server’s response to an HTTP request.
The apparent encoding, provided by the chardet library.
Releases the connection back to the pool. Once this method has been called the underlying raw object must not be accessed again.
Note: Should not normally need to be called explicitly.
Content of the response, in bytes.
A CookieJar of Cookies the server sent back.
The amount of time elapsed between sending the request and the arrival of the response (as a timedelta). This property specifically measures the time taken between sending the first byte of the request and finishing parsing the headers. It is therefore unaffected by consuming the response content or the value of the stream keyword argument.
Encoding to decode with when accessing r.text.
Case-insensitive Dictionary of Response Headers. For example, headers[‘content-encoding’] will return the value of a ‘Content-Encoding’ response header.
A list of Response objects from the history of the Request. Any redirect responses will end up here. The list is sorted from the oldest to the most recent request.
True if this Response one of the permanent versions of redirect.
True if this Response is a well-formed HTTP redirect that could have been processed automatically (by Session.resolve_redirects ).
iter_content ( chunk_size=1, decode_unicode=False ) [source] ¶
Iterates over the response data. When stream=True is set on the request, this avoids reading the content at once into memory for large responses. The chunk size is the number of bytes it should read into memory. This is not necessarily the length of each item returned as decoding can take place.
chunk_size must be of type int or None. A value of None will function differently depending on the value of stream . stream=True will read data as it arrives in whatever size the chunks are received. If stream=False, data is returned as a single chunk.
If decode_unicode is True, content will be decoded using the best available encoding based on the response.
iter_lines ( chunk_size=512, decode_unicode=False, delimiter=None ) [source] ¶
Iterates over the response data, one line at a time. When stream=True is set on the request, this avoids reading the content at once into memory for large responses.
This method is not reentrant safe.
Returns the json-encoded content of a response, if any.
Parameters: | **kwargs – Optional arguments that json.loads takes. |
---|---|
Raises: | ValueError – If the response body does not contain valid json. |
links ¶
Returns the parsed header links of the response, if any.
Returns a PreparedRequest for the next request in a redirect chain, if there is one.
Returns True if status_code is less than 400, False if not.
This attribute checks if the status code of the response is between 400 and 600 to see if there was a client error or a server error. If the status code is between 200 and 400, this will return True. This is not a check to see if the response code is 200 OK .
Raises stored HTTPError , if one occurred.
Textual reason of responded HTTP Status, e.g. “Not Found” or “OK”.
The PreparedRequest object to which this is a response.
Integer Code of responded HTTP Status, e.g. 404 or 200.
Content of the response, in unicode.
If Response.encoding is None, encoding will be guessed using chardet .
The encoding of the response content is determined based solely on HTTP headers, following RFC 2616 to the letter. If you can take advantage of non-HTTP knowledge to make a better guess at the encoding, you should set r.encoding appropriately before accessing this property.
Final URL location of Response.
Lower-Lower-Level Classes¶
The fully mutable PreparedRequest object, containing the exact bytes that will be sent to the server.
Generated from either a Request object or manually.
request body to send to the server.
deregister_hook ( event, hook ) ¶
Deregister a previously registered hook. Returns True if the hook existed, False if not.
dictionary of HTTP headers.
dictionary of callback hooks, for internal usage.
HTTP verb to send to the server.
Build the path URL to use.
Prepares the entire request with the given parameters.
Prepares the given HTTP auth data.
Prepares the given HTTP body data.
Prepare Content-Length header based on request method and body
Prepares the given HTTP cookie data.
This function eventually generates a Cookie header from the given cookies using cookielib. Due to cookielib’s design, the header will not be regenerated if it already exists, meaning this function can only be called once for the life of the PreparedRequest object. Any subsequent calls to prepare_cookies will have no actual effect, unless the “Cookie” header is removed beforehand.
Prepares the given HTTP headers.
Prepares the given hooks.
Prepares the given HTTP method.
Prepares the given HTTP URL.
Properly register a hook.
HTTP URL to send the request to.
class requests.adapters. BaseAdapter [source] ¶
The Base Transport Adapter
Cleans up adapter specific items.
Sends PreparedRequest object. Returns Response object.
Parameters: |
|
---|
class requests.adapters. HTTPAdapter ( pool_connections=10, pool_maxsize=10, max_retries=0, pool_block=False ) [source]¶
The built-in HTTP Adapter for urllib3.
Provides a general-case interface for Requests sessions to contact HTTP and HTTPS urls by implementing the Transport Adapter interface. This class will usually be created by the Session class under the covers.
Parameters: |
|
---|
Add any headers needed by the connection. As of v2.0 this does nothing by default, but is left for overriding by users that subclass the HTTPAdapter .
This should not be called from user code, and is only exposed for use when subclassing the HTTPAdapter .
Parameters: |
|
---|
build_response ( req, resp ) [source]¶
Builds a Response object from a urllib3 response. This should not be called from user code, and is only exposed for use when subclassing the HTTPAdapter
Parameters: |
|
---|---|
Return type: |
cert_verify ( conn, url, verify, cert ) [source] ¶
Verify a SSL certificate. This method should not be called from user code, and is only exposed for use when subclassing the HTTPAdapter .
Parameters: |
|
---|
close ( ) [source]¶
Disposes of any internal state.
Currently, this closes the PoolManager and any active ProxyManager, which closes any pooled connections.
Returns a urllib3 connection for the given URL. This should not be called from user code, and is only exposed for use when subclassing the HTTPAdapter .
Parameters: |
|
---|---|
Return type: |
init_poolmanager ( connections, maxsize, block=False, **pool_kwargs ) [source] ¶
Initializes a urllib3 PoolManager.
This method should not be called from user code, and is only exposed for use when subclassing the HTTPAdapter .
Parameters: |
|
---|
proxy_headers ( proxy ) [source]¶
Returns a dictionary of the headers to add to any request sent through a proxy. This works with urllib3 magic to ensure that they are correctly sent to the proxy, rather than in a tunnelled request if CONNECT is being used.
This should not be called from user code, and is only exposed for use when subclassing the HTTPAdapter .
Parameters: | proxy – The url of the proxy being used for this request. |
---|---|
Return type: | dict |
proxy_manager_for ( proxy, **proxy_kwargs ) [source] ¶
Return urllib3 ProxyManager for the given proxy.
This method should not be called from user code, and is only exposed for use when subclassing the HTTPAdapter .
Parameters: |
|
---|---|
Returns: |
request_url ( request, proxies ) [source] ¶
Obtain the url to use when making the final request.
If the message is being sent through a HTTP proxy, the full URL has to be used. Otherwise, we should only use the path portion of the URL.
This should not be called from user code, and is only exposed for use when subclassing the HTTPAdapter .
Parameters: |
|
---|---|
Return type: |
send ( request, stream=False, timeout=None, verify=True, cert=None, proxies=None ) [source] ¶
Sends PreparedRequest object. Returns Response object.
Authentication¶
Base class that all auth implementations derive from
class requests.auth. HTTPBasicAuth ( username, password ) [source] ¶
Attaches HTTP Basic Authentication to the given Request object.
class requests.auth. HTTPDigestAuth ( username, password ) [source] ¶
Attaches HTTP Digest Authentication to the given Request object.
Encodings¶
Returns encodings from given content string.
Parameters: |
|
---|---|
Return type: |
Parameters: | content – bytestring to extract encodings from. |
---|
requests.utils. get_encoding_from_headers ( headers ) [source] ¶
Returns encodings from given HTTP Header Dict.
Parameters: | headers – dictionary to extract encoding from. |
---|---|
Return type: | str |
requests.utils. get_unicode_from_response ( r ) [source] ¶
Returns the requested content back in unicode.
Parameters: | r – Response object to get unicode content from. |
---|
- charset from content-type
- fall back and replace all unicode characters
Return type: | str |
---|
Cookies¶
Returns a key/value dictionary from a CookieJar.
Parameters: | cj – CookieJar object to extract cookies from. |
---|---|
Return type: | dict |
requests.utils. add_dict_to_cookiejar ( cj, cookie_dict ) [source] ¶
Returns a CookieJar from a key/value dictionary.
Parameters: |
|
---|---|
Return type: |
requests.cookies. cookiejar_from_dict ( cookie_dict, cookiejar=None, overwrite=True ) [source] ¶
Returns a CookieJar from a key/value dictionary.
Parameters: |
|
---|---|
Return type: |
class requests.cookies. RequestsCookieJar ( policy=None ) [source] ¶
Compatibility class; is a cookielib.CookieJar, but exposes a dict interface.
This is the CookieJar we create by default for requests and sessions that don’t specify one, since some clients may expect response.cookies and session.cookies to support dict operations.
Requests does not use the dict interface internally; it’s just for compatibility with external client code. All requests code should work out of the box with externally provided instances of CookieJar , e.g. LWPCookieJar and FileCookieJar .
Unlike a regular CookieJar, this class is pickleable.
dictionary operations that are normally O(1) may be O(n).
Add correct Cookie: header to request (urllib.request.Request object).
The Cookie2 header is also added unless policy.hide_cookie2 is true.
Clear some cookies.
Invoking this method without arguments will clear all cookies. If given a single argument, only cookies belonging to that domain will be removed. If given two arguments, cookies belonging to the specified path within that domain are removed. If given three arguments, then the cookie with the specified name, path and domain is removed.
Raises KeyError if no matching cookie exists.
Discard all expired cookies.
You probably don’t need to call this method: expired cookies are never sent back to the server (provided you’re using DefaultCookiePolicy), this method is called by CookieJar itself every so often, and the .save() method won’t save expired cookies anyway (unless you ask otherwise by passing a true ignore_expires argument).
Discard all session cookies.
Note that the .save() method won’t save session cookies anyway, unless you ask otherwise by passing a true ignore_discard argument.
Return a copy of this RequestsCookieJar.
extract_cookies ( response, request ) ¶
Extract cookies from response, where allowable given the request.
Dict-like get() that also supports optional domain and path args in order to resolve naming collisions from using one cookie jar over multiple domains.
operation is O(n), not O(1).
Takes as an argument an optional domain and path and returns a plain old Python dict of name-value pairs of cookies that meet the requirements.
Return type: | dict |
---|
get_policy ( ) [source] ¶
Return the CookiePolicy instance used.
Dict-like items() that returns a list of name-value tuples from the jar. Allows client-code to call dict(RequestsCookieJar) and get a vanilla python dict of key value pairs.
keys() and values().
Dict-like iteritems() that returns an iterator of name-value tuples from the jar.
iterkeys() and itervalues().
Dict-like iterkeys() that returns an iterator of names of cookies from the jar.
itervalues() and iteritems().
Dict-like itervalues() that returns an iterator of values of cookies from the jar.
iterkeys() and iteritems().
Dict-like keys() that returns a list of names of cookies from the jar.
values() and items().
Utility method to list all the domains in the jar.
Utility method to list all the paths in the jar.
make_cookies ( response, request ) ¶
Return sequence of Cookie objects extracted from response object.
Returns True if there are multiple domains in the jar. Returns False otherwise.
Return type: | bool |
---|
pop ( k [ , d ] ) → v, remove specified key and return the corresponding value.¶
If key is not found, d is returned if given, otherwise KeyError is raised.
popitem ( ) → (k, v), remove and return some (key, value) pair¶
as a 2-tuple; but raise KeyError if D is empty.
Dict-like set() that also supports optional domain and path args in order to resolve naming collisions from using one cookie jar over multiple domains.
Set a cookie, without checking whether or not it should be set.
set_cookie_if_ok ( cookie, request ) ¶
Set a cookie if policy says it’s OK to do so.
setdefault ( k [ , d ] ) → D.get(k,d), also set D[k]=d if k not in D¶ update ( other ) [source] ¶
Updates this jar with cookies from another CookieJar or dict-like
Dict-like values() that returns a list of values of cookies from the jar.
There are two cookies that meet the criteria specified in the cookie jar. Use .get and .set and include domain and path args in order to be more specific.
Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.
Status Code Lookup¶
The codes object defines a mapping from common names for HTTP statuses to their numerical codes, accessible either as attributes or as dictionary items.
Some codes have multiple names, and both upper- and lower-case versions of the names are allowed. For example, codes.ok , codes.OK , and codes.okay all correspond to the HTTP status code 200.
- 100: continue
- 101: switching_protocols
- 102: processing
- 103: checkpoint
- 122: uri_too_long , request_uri_too_long
- 200: ok , okay , all_ok , all_okay , all_good , o/ , ✓
- 201: created
- 202: accepted
- 203: non_authoritative_info , non_authoritative_information
- 204: no_content
- 205: reset_content , reset
- 206: partial_content , partial
- 207: multi_status , multiple_status , multi_stati , multiple_stati
- 208: already_reported
- 226: im_used
- 300: multiple_choices
- 301: moved_permanently , moved , o-
- 302: found
- 303: see_other , other
- 304: not_modified
- 305: use_proxy
- 306: switch_proxy
- 307: temporary_redirect , temporary_moved , temporary
- 308: permanent_redirect , resume_incomplete , resume
- 400: bad_request , bad
- 401: unauthorized
- 402: payment_required , payment
- 403: forbidden
- 404: not_found , -o-
- 405: method_not_allowed , not_allowed
- 406: not_acceptable
- 407: proxy_authentication_required , proxy_auth , proxy_authentication
- 408: request_timeout , timeout
- 409: conflict
- 410: gone
- 411: length_required
- 412: precondition_failed , precondition
- 413: request_entity_too_large
- 414: request_uri_too_large
- 415: unsupported_media_type , unsupported_media , media_type
- 416: requested_range_not_satisfiable , requested_range , range_not_satisfiable
- 417: expectation_failed
- 418: im_a_teapot , teapot , i_am_a_teapot
- 421: misdirected_request
- 422: unprocessable_entity , unprocessable
- 423: locked
- 424: failed_dependency , dependency
- 425: unordered_collection , unordered
- 426: upgrade_required , upgrade
- 428: precondition_required , precondition
- 429: too_many_requests , too_many
- 431: header_fields_too_large , fields_too_large
- 444: no_response , none
- 449: retry_with , retry
- 450: blocked_by_windows_parental_controls , parental_controls
- 451: unavailable_for_legal_reasons , legal_reasons
- 499: client_closed_request
- 500: internal_server_error , server_error , /o , ✗
- 501: not_implemented
- 502: bad_gateway
- 503: service_unavailable , unavailable
- 504: gateway_timeout
- 505: http_version_not_supported , http_version
- 506: variant_also_negotiates
- 507: insufficient_storage
- 509: bandwidth_limit_exceeded , bandwidth
- 510: not_extended
- 511: network_authentication_required , network_auth , network_authentication
Migrating to 1.x¶
This section details the main differences between 0.x and 1.x and is meant to ease the pain of upgrading.
API Changes¶
Response.json is now a callable and not a property of a response.
The Session API has changed. Sessions objects no longer take parameters. Session is also now capitalized, but it can still be instantiated with a lowercase session for backwards compatibility.
All request hooks have been removed except ‘response’.
Authentication helpers have been broken out into separate modules. See requests-oauthlib and requests-kerberos.
The parameter for streaming requests was changed from prefetch to stream and the logic was inverted. In addition, stream is now required for raw response reading.
The config parameter to the requests method has been removed. Some of these options are now configured on a Session such as keep-alive and maximum number of redirects. The verbosity option should be handled by configuring logging.
Licensing¶
One key difference that has nothing to do with the API is a change in the license from the ISC license to the Apache 2.0 license. The Apache 2.0 license ensures that contributions to Requests are also covered by the Apache 2.0 license.
Migrating to 2.x¶
Compared with the 1.0 release, there were relatively few backwards incompatible changes, but there are still a few issues to be aware of with this major release.
For more details on the changes in this release including new APIs, links to the relevant GitHub issues and some of the bug fixes, read Cory’s blog on the subject.
API Changes¶
There were a couple changes to how Requests handles exceptions. RequestException is now a subclass of IOError rather than RuntimeError as that more accurately categorizes the type of error. In addition, an invalid URL escape sequence now raises a subclass of RequestException rather than a ValueError .
Lastly, httplib.IncompleteRead exceptions caused by incorrect chunked encoding will now raise a Requests ChunkedEncodingError instead.
The proxy API has changed slightly. The scheme for a proxy URL is now required.
Behavioural Changes¶
- Keys in the headers dictionary are now native strings on all Python versions, i.e. bytestrings on Python 2 and unicode on Python 3. If the keys are not native strings (unicode on Python 2 or bytestrings on Python 3) they will be converted to the native string type assuming UTF-8 encoding.
- Values in the headers dictionary should always be strings. This has been the project’s position since before 1.0 but a recent change (since version 2.11.0) enforces this more strictly. It’s advised to avoid passing header values as unicode when possible.
Requests is an elegant and simple HTTP library for Python, built for human beings. You are currently looking at the documentation of the development release.
Stay Informed
Receive updates on new releases and upcoming projects.
Источник
The clear, simple syntax of Python makes it an ideal language to interact with REST APIs, and in typical Python fashion, there’s a library made specifically to provide that functionality: Requests. Python Requests is a powerful tool that provides the simple elegance of Python to make HTTP requests to any API in the world. At Nylas, we built our REST APIs for email, calendar, and contacts on Python, and we process over 500 million API requests a day, so naturally, we depend a ton on the Python Requests library.
In this guide, we’ll take a comprehensive look at making HTTP requests with Python Requests and learn how to use this functionality to integrate with REST APIs.
Contents:
- The roles of HTTP, APIs, and REST
- How to use Python Requests with REST APIs
- How to authenticate to a REST API
- How to handle HTTP errors with Python Requests
- How to make robust API Requests
The Roles of HTTP, APIs, and REST
An Application Programming Interface (API) is a web service that grants access to specific data and methods that other applications can access – and sometimes edit – via standard HTTP protocols, just like a website. This simplicity makes it easy to quickly integrate APIs into a wide variety of applications. REpresentational State Transfer (REST), is probably the most popular architectural style of APIs for web services. It consists of a set of guidelines designed to simplify client / server communication. REST APIs make data access much more straightforward and logical.
The Request
When you want to interact with data via a REST API, this is called a request. A request is made up of the following components:
Endpoint – The URL that delineates what data you are interacting with. Similar to how a web page URL is tied to a specific page, an endpoint URL is tied to a specific resource within an API.
Method – Specifies how you’re interacting with the resource located at the provided endpoint. REST APIs can provide methods to enable full Create, Read, Update, and Delete (CRUD) functionality. Here are common methods most REST APIs provide:
- GET – Retrieve data
- PUT – Replace data
- POST – Create data
- DELETE – Delete data
Data – If you’re using a method that involves changing data in a REST API, you’ll need to include a data payload with the request that includes all data that will be created or modified.
Headers – Contain any metadata that needs to be included with the request, such as authentication tokens, the content type that should be returned, and any caching policies.
The Response
When you perform a request, you’ll get a response from the API. Just like in the request, it’ll have a response header and response data, if applicable. The response header consists of useful metadata about the response, while the response data returns what you actually requested. This can be any sort of data, as it’s really dependent on the API. The text is usually returned as JSON, but other markdown languages like XML are also possible.
Let’s look at a simple example of a request and a response. In the terminal, we’ll use curl to make a GET request to the Open Notify API. This is a simple, yet nifty API that has information about astronauts that are currently in space:
curl -X GET "http://api.open-notify.org/astros.json"
You should see a response in JSON format that lists data about these astronauts, at the time of this article there are three people on a historic trip to the International Space Station:
{ "number": 3, "message": "success", "people": [ { "craft": "ISS", "name": "Chris Cassidy" }, { "craft": "ISS", "name": "Anatoly Ivanishin" }, { "craft": "ISS", "name": "Ivan Vagner" } ] }
How to Use Python Requests with REST APIs
Now, let’s take a look at what it takes to integrate with a REST API using Python Requests. First, you’ll need to have the necessary software; make sure you have Python and pip installed on your machine. Then, head over to the command line and install the python requests module with pip:
pip install requests
Now you’re ready to start using Python Requests to interact with a REST API, make sure you import the Requests library into any scripts you want to use it in:
import requests
How Request Data With GET
The GET method is used to access data for a specific resource from a REST API; Python Requests includes a function to do exactly this.
import requests response = requests.get("http://api.open-notify.org/astros.json") print(response) >>>> Response<200>
The response object contains all the data sent from the server in response to your GET request, including headers and the data payload. When this code example prints the response object to the console it simply returns the name of the object’s class and the status code the request returned (more on status codes later).
While this information might be useful, you’re most likely interested in the content of the request itself, which can be accessed in a few ways:
response.content() # Return the raw bytes of the data payload response.text() # Return a string representation of the data payload response.json() # This method is convenient when the API returns JSON
How to Use Query Parameters
Queries can be used to filter the data that an API returns, and these are added as query parameters that are appended to the endpoint URL. With Python Requests, this is handled via the params argument, which accepts a dictionary object; let’s see what that looks like when we use the Open Notify API to GET an estimate for when the ISS will fly over a specified point:
query = {'lat':'45', 'lon':'180'} response = requests.get('http://api.open-notify.org/iss-pass.json', params=query) print(response.json())
The print command would return something that looks like this:
{ 'message': 'success', 'request': { 'altitude': 100, 'datetime': 1590607799, 'latitude': 45.0, 'longitude': 180.0, 'passes': 5 }, 'response': [ {'duration': 307, 'risetime': 1590632341}, {'duration': 627, 'risetime': 1590637934}, {'duration': 649, 'risetime': 1590643725}, {'duration': 624, 'risetime': 1590649575}, {'duration': 643, 'risetime': 1590655408} ] }
How to Create and Modify Data With POST and PUT
In a similar manner as the query parameters, you can use the data argument to add the associated data for PUT and POST method requests.
# Create a new resource response = requests.post('https://httpbin.org/post', data = {'key':'value'}) # Update an existing resource requests.put('https://httpbin.org/put', data = {'key':'value'})
Experience Nylas REST APIs Using Python
Get a quick demonstration on how to make your first API call in minutes with Nylas using Python.
How to Access REST Headers
You can also retrieve metadata from the response via headers. For example, to view the date of the response, just specify that with the `headers` property:
print(response.headers["date"]) >>>> 'Wed, 11 June 2020 19:32:24 GMT'
For open APIs, that covers the basics. However, many APIs can’t be used by just anyone. For those, let’s go over how to authenticate to REST APIs.
How to Authenticate to a REST API
So far you’ve seen how to interact with open REST APIs that don’t require any authorization. However, many REST APIs require you to authenticate to them before you can access specific endpoints, particularly if they deal with sensitive data.
There are a few common authentication methods for REST APIs that can be handled with Python Requests. The simplest way is to pass your username and password to the appropriate endpoint as HTTP Basic Auth; this is equivalent to typing your username and password into a website.
requests.get( 'https://api.github.com/user', auth=HTTPBasicAuth('username', 'password') )
A more secure method is to get an access token that acts as an equivalent to a username/password combination; the method to get an access token varies widely from API to API, but the most common framework for API authentication is OAuth. Here at Nylas, we use three-legged OAuth to grant an access token for user accounts that is restricted to scopes that define the specific data and functionality that can be accessed. This process is demonstrated in the Nylas Hosted Auth service.
Once you have an access token, you can provide it as a bearer token in the request header: this is the most secure way to authenticate to a REST API with an access token:
my_headers = {'Authorization' : 'Bearer {access_token}'} response = requests.get('http://httpbin.org/headers', headers=my_headers)
There are quite a few other methods to authenticate to a REST API, including digest, Kerberos, NTLM, and AuthBase. The use of these depends on the architecture decisions of the REST API producer.
Use Sessions to Manage Access Tokens
Session objects come in handy when working with Python Requests as a tool to persist parameters that are needed for making multiple requests within a single session, like access tokens. Also, managing session cookies can provide a nice performance increase because you don’t need to open a new connection for every request.
session = requests.Session() session.headers.update({'Authorization': 'Bearer {access_token}'}) response = session.get('https://httpbin.org/headers')
How to Handle HTTP Errors With Python Requests
API calls don’t always go as planned, and there’s a multitude of reasons why API requests might fail that could be the fault of either the server or the client. If you’re going to use a REST API, you need to understand how to handle the errors they output when things go wrong to make your code more robust. This section covers everything you need to know about handling HTTP errors with Python Requests.
The Basics of HTTP Status Codes
Before we dive into the specifics of Python Requests, we first need to take a step back and understand what HTTP status codes are and how they relate to errors you might encounter.
All status codes fall into one of five categories.
- 1xx Informational – Indicates that a request has been received and that the client should continue to make the requests for the data payload. You likely won’t need to worry about these status codes while working with Python Requests.
- 2xx Successful – Indicates that a requested action has been received, understood, and accepted. You can use these codes to verify the existence of data before attempting to act on it.
- 3xx Redirection – Indicates that the client must make an additional action to complete the request like accessing the resource via a proxy or a different endpoint. You may need to make additional requests, or modify your requests to deal with these codes.
- 4xx Client Error – Indicates problems with the client, such as a lack of authorization, forbidden access, disallowed methods, or attempts to access nonexistent resources. This usually indicates configuration errors on the client application.
- 5xx Server Error – Indicates problems with the server that provides the API. There are a large variety of server errors and they often require the API provider to resolve.
How to Check for HTTP Errors With Python Requests
The response objects has a status_code attribute that can be used to check for any errors the API might have reported. The next example shows how to use this attribute to check for successful and 404 not found HTTP status codes, and you can use this same format for all HTTP status codes.
response = requests.get("http://api.open-notify.org/astros.json") if (response.status_code == 200): print("The request was a success!") # Code here will only run if the request is successful elif (response.status_code == 404: print("Result not found!") # Code here will react to failed requests
To see this in action, try removing the last letter from the URL endpoint, the API should return a 404 status code.
If you want requests to raise an exception for all error codes (4xx and 5xx), you can use the raise_for_status() function and catch specific errors using Requests built-in exceptions. This next example accomplishes the same thing as the previous code example.
try: response = requests.get('http://api.open-notify.org/astros.json') response.raise_for_status() # Additional code will only run if the request is successful except requests.exceptions.HTTPError as error: print(error) # This code will run if there is a 404 error.
TooManyRedirects
Something that is often indicated by 3xx HTTP status codes is the requirement to redirect to a different location for the resource you’re requesting. This can sometimes result in a situation where you end up with an infinite redirect loop. The Python Requests module has the TooManyRedirects error that you can use to handle this problem. To resolve this problem, it’s likely the URL you’re using to access the resource is wrong and needs to be changed.
try: response = requests.get('http://api.open-notify.org/astros.json') response.raise_for_status() # Code here will only run if the request is successful except requests.exceptions.TooManyRedirects as error: print(error)
You can optionally use the request options to set the maximum number of redirects:
response = requests.get('http://api.open-notify.org/astros.json', max_redirects=2)
Or disable redirecting completely within your request options:
response = requests.get('http://api.open-notify.org/astros.json', allow_redirects=False)
ConnectionError
So far, we’ve only looked at errors that come from an active server. What happens if you don’t receive a response from the server at all? Connection errors can occur for many different reasons, including a DNS failure, refused connection, internet connectivity issues or latency somewhere in the network. Python Requests offers the ConnectionError exception that indicates when your client is unable to connect to the server.
try: response = requests.get('http://api.open-notify.org/astros.json') # Code here will only run if the request is successful except requests.ConnectionError as error: print(error)
This type of error might be temporary, or permanent. In the former scenario, you should retry the request again to see if there is a different result. In the latter scenario, you should make sure you’re able to deal with a prolonged inability to access data from the API, and it might require you to investigate your own connectivity issues.
Timeout
Timeout errors occur when you’re able to connect to the API server, but it doesn’t complete the request within the allotted amount of time. Similar to the other errors we’ve looked at, Python Requests can handle this error with a Timeout exception:
try: response = requests.get('http://api.open-notify.org/astros.json', timeout=0.00001) # Code here will only run if the request is successful except requests.Timeout as error: print(error)
In this example, the timeout was set as a fraction of a second via the request options. Most APIs are unable to respond this quickly, so the code will produce a timeout exception. You can avoid this error by setting longer timeouts for your script, optimizing your requests to be smaller, or setting up a retry loop for the request. This can also sometimes indicate a problem with the API provider. One final solution is to incorporate asynchronous API calls to prevent your code from stopping while it waits for larger responses.
How to Make Robust API Requests
As we’ve seen, the Requests module elegantly handles common API request errors by utilizing exception handling in Python. If we put all of the errors we’ve talked about together, we have a rather seamless way to handle any HTTP request error that comes our way:
try: response = requests.get('http://api.open-notify.org/astros.json', timeout=5) response.raise_for_status() # Code here will only run if the request is successful except requests.exceptions.HTTPError as errh: print(errh) except requests.exceptions.ConnectionError as errc: print(errc) except requests.exceptions.Timeout as errt: print(errt) except requests.exceptions.RequestException as err: print(err)
Experience Nylas REST APIs Using Python
Sign up for a Nylas developer account and make your first API call in minutes using Python.
Learn More About Python
If you’ve made it this far, congrats! You’re well on your way to becoming a Python Requests wizard for whom no REST API is too great a match. Want to keep learning? We have tons of knowledgable Python experts here at Nylas, and we have in-depth content on our blog about packaging and deploying Python code to production, and using environment variables to make your Python code more secure.
Don’t miss the action and watch our livestream Coding with Nylas: