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 tutorial, you’ll see some of the most useful features that Requests has to offer as well as ways to customize and optimize those features for different situations that 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
For the best experience working through this tutorial, you should have basic general knowledge of HTTP. That said, you still may be able to follow along fine without it.
In the upcoming sections, you’ll see how you can install and use requests in your application. If you want to play with the code examples that you’ll see in this tutorial, as well as some additional ones, then you can download the code examples and work with them locally:
Get Your Code: Click here to download the free sample code that shows you how to use Python’s Requests library.
Take the Quiz: Test your knowledge with our interactive “HTTP Requests With the "requests" Library” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
HTTP Requests With the "requests" LibraryTest your understanding of the Python "requests" library for making HTTP requests and interacting with web services.
Getting Started With Python’s Requests Library
Even though the Requests library is a common staple for many Python developers, it’s not included in Python’s standard library. There are good reasons for that decision, primarily that the library can continue to evolve more freely as a self-standing project.
Note: Requests doesn’t support asynchronous HTTP requests directly. If you need async support in your program, you should try out AIOHTTP or HTTPX. The latter library is broadly compatible with Requests’ syntax.
Because Requests is a third-party library, you need to install it before you can use it in your code. As a good practice, you should install external packages into a virtual environment, but you may choose to install requests into your global environment if you’re planning to use it across multiple projects.
Whether you’re working in a virtual environment or not, you’ll need to install requests:
$ python -m pip install requests
Once pip has finished installing requests, you can use it in your application. Importing requests looks like this:
import requests
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 using Requests, you can invoke requests.get().
To test this out, you can make a GET request to GitHub’s REST API by calling get() with the following URL:
>>> import requests
>>> requests.get("https://api.github.com")
<Response [200]>
Congratulations! You’ve made your first request. Now you’ll 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. 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:
>>> import requests
>>> 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 wasn’t 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 200, which means that your request was successful and the server responded with the data that 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, then your program will print Success!. If the result is a 404, then 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, then it’ll evaluate to True if the status code was smaller than 400, and False otherwise.
Therefore, you can simplify the last example by rewriting the if statement:
if response:
print("Success!")
else:
raise Exception(f"Non-success status code: {response.status_code}")
In the code snippet above, you implicitly check whether the .status_code of response is between 200 and 399. If it’s not, then you raise an exception that includes the non-success status code in an f-string.
Note: This truth value test is possible because .__bool__() is an overloaded method on Response. This means that the adapted default behavior of Response takes the status code into account when determining the truth value of the object.
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 399 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 status code 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. Then, if necessary, you’ll need to 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 use Request’s built-in capacities to raise an exception if the request was unsuccessful. You can do this using .raise_for_status():
raise_error.py
import requests
from requests.exceptions import HTTPError
URLS = ["https://api.github.com", "https://api.github.com/invalid"]
for url in URLS:
try:
response = requests.get(url)
response.raise_for_status()
except HTTPError as http_err:
print(f"HTTP error occurred: {http_err}")
except Exception as err:
print(f"Other error occurred: {err}")
else:
print("Success!")
If you invoke .raise_for_status(), then Requests will raise an HTTPError for status codes between 400 and 600. If the status code indicates a successful request, then the program will proceed without raising that exception.
Now, you know a lot about how to deal with the status code of the response that 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:
>>> import requests
>>> response = requests.get("https://api.github.com")
>>> response.content
b'{"current_user_url":"https://api.github.com/user", ...}'
>>> type(response.content)
<class 'bytes'>
While .content gives you access to the raw bytes of the response payload, you’ll 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", ...}'
>>> type(response.text)
<class 'str'>
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 don’t specify one. You can provide an explicit encoding by setting .encoding before accessing .text:
>>> response.encoding = "utf-8" # Optional: Requests infers this.
>>> response.text
'{"current_user_url":"https://api.github.com/user", ...}'
If you take a look at the response, then you’ll see that it’s actually serialized JSON content. To get a dictionary, you could take the str that 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', ...}
>>> type(response.json())
<class 'dict'>
The type of the return value of .json() is a dictionary, so you can access values in the object by key:
>>> response_dict = response.json()
>>> response_dict["emojis_url"]
'https://api.github.com/emojis'
You can do a lot with status codes and message bodies. But, if you need more information, like metadata about the response itself, then you’ll need to look at the response’s headers.
Headers
The response headers can give you useful information, such as the content type of the response payload and a time limit on how long to cache the response. To view these headers, access .headers:
>>> import requests
>>> response = requests.get("https://api.github.com")
>>> response.headers
{'Server': 'GitHub.com',
...
'X-GitHub-Request-Id': 'AE83:3F40:2151C46:438A840:65C38178'}
.headers returns a dictionary-like object, allowing you to access header values by key. For example, to see the content type of the response payload, you can access "Content-Type":
>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
There’s something special about this dictionary-like headers object, though. The HTTP specification defines headers as case-insensitive, which means that you’re able to access these headers without worrying about their capitalization:
>>> response.headers["content-type"]
'application/json; charset=utf-8'
Whether you use the key "content-type" or "Content-Type", you’ll get the same value.
Now that you’ve seen the most useful attributes and methods of Response in action, you already have a good overview of Requests’ basic usage. You can get content from the Internet and work with the response that you receive.
But there’s more to the Internet than plain and straightforward URLs. In the next section, you’ll take a step back and see how your responses change when you customize your GET requests to account for query string parameters.
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 repository search API to look for popular Python repositories:
search_popular_repos.py
import requests
# Search GitHub's repositories for popular Python projects
response = requests.get(
"https://api.github.com/search/repositories",
params={"q": "language:python", "sort": "stars", "order": "desc"},
)
# Inspect some attributes of the first three repositories
json_response = response.json()
popular_repositories = json_response["items"]
for repo in popular_repositories[:3]:
print(f"Name: {repo['name']}")
print(f"Description: {repo['description']}")
print(f"Stars: {repo['stargazers_count']}")
print()
By passing a dictionary to the params parameter of get(), you’re 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’ve just done, or as a list of tuples:
>>> import requests
>>> requests.get(
... "https://api.github.com/search/repositories",
... [("q", "language:python"), ("sort", "stars"), ("order", "desc")],
... )
<Response [200]>
You can even pass the values as bytes:
>>> requests.get(
... "https://api.github.com/search/repositories",
... params=b"q=language:python&sort=stars&order=desc",
... )
<Response [200]>
Query strings are useful for parameterizing GET requests. Another way to customize your requests is by adding or modifying the headers that you send.
Request Headers
To customize headers, you pass a dictionary of HTTP headers to get() using the headers parameter. For example, you can change your previous search request to highlight matching search terms in the results by specifying the text-match media type in the Accept header:
text_matches.py
import requests
response = requests.get(
"https://api.github.com/search/repositories",
params={"q": '"real python"'},
headers={"Accept": "application/vnd.github.text-match+json"},
)
# View the new `text-matches` list which provides information
# about your search term within the results
json_response =