Python’s Requests Library (Guide)

Python's Requests Library (Guide)

by Alex Ronquillo Updated Reading time estimate 30m intermediate web-dev

The Requests library is the go-to package for making HTTP requests in Python. It abstracts the complexities of making requests behind an intuitive API. Though not part of Python’s standard library, it’s worth considering Requests to perform HTTP actions like GET, POST, and more.

By the end of this tutorial, you’ll understand that:

  • Requests is not a built-in Python module—it’s a third-party library that you must install separately.
  • You make a GET request in Python using requests.get() with the desired URL.
  • To add headers to requests, pass a dictionary of headers to the headers parameter in your request.
  • To send POST data, use the data parameter for form-encoded data or the json parameter for JSON data.
  • response.text gives you a string representation of the response content, while response.content provides raw bytes.

This tutorial guides you through customizing requests with headers and data, handling responses, authentication, and optimizing performance using sessions and retries.

If you want to explore the code examples that you’ll see in this tutorial, then you can download them here:

Take the Quiz: Test your knowledge with our interactive “Python's Requests Library” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Python's Requests Library

Test your understanding of the Python Requests library for making HTTP requests and interacting with web services.

Get 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. That way, the library can continue to evolve more freely as a self-standing project.

Since Requests is a third-party library, you need to install it before using 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 plan to use it across multiple projects.

Whether you’re working in a virtual environment or not, you’ll need to install requests:

Language: Shell
$ python -m pip install requests==2.32.4

Once pip finishes installing requests, you can use it in your application. Importing requests looks like this:

Language: Python
import requests

Now that you’re all set up, it’s time to begin your journey with Requests. Your first goal will be to make a GET request.

Make a GET Request

HTTP methods, such as GET and POST, specify the action you want to perform when making an HTTP request. In addition to GET and POST, there are several other common methods that you’ll use later in this tutorial.

One of the most commonly used HTTP methods is GET, which retrieves data from a specified resource. To make a GET request using Requests, you can invoke requests.get().

To try this out, you can make a GET request to GitHub’s REST API by calling get() with the following URL:

Language: Python
>>> 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.

Inspect the Response

A Response is the object that contains the results of your request. Try making that same request again, but this time store the return value in a variable so you can get a closer look at its attributes and behaviors:

Language: Python
>>> import requests
>>> response = requests.get("https://api.github.com")

In this example, you’ve captured the return value of requests.get(). It’s an instance of Response, and you 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.

Work With Status Codes

The first piece 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, while 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:

Language: Python
>>> response.status_code
200

.status_code returned 200, which means that your request was successful and the server responded with the data you requested.

Sometimes, you might want to use this information to make decisions in your code:

Language: Python
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 Boolean context, such as a conditional statement, then it’ll evaluate to True when the status code is less than 400, and False otherwise.

That means you can modify the last example by rewriting the if statement:

Language: Python
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 with an error message that includes the non-success status code wrapped in an f-string.

Keep in mind that this method does not verify whether the status code is equal to 200. This is because other status codes within the 200 to 399 range, such as 204 NO CONTENT and 304 NOT MODIFIED, are also considered successful because 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 whether the request was generally successful. Then, if necessary, you’ll need to handle the response appropriately based on the status code.

Suppose 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():

Language: Python Filename: 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 you receive 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 learn how to view the actual data that the server sent back in the body of the response.

Access 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 formats.

To see the response’s content in bytes, you use .content:

Language: Python
>>> 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:

Language: Python
>>> 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:

Language: Python
>>> 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, the direct way to accomplish this task is to use .json():

Language: Python
>>> 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:

Language: Python
>>> 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, you’ll need to look at the response’s headers.

View Response Headers

The response headers can give you useful information, such as the content type of the response payload and how long to cache the response. To view these headers, access .headers:

Language: Python
>>> import requests

>>> response = requests.get("https://api.github.com")
>>> response.headers
{'Server': 'github.com',
...
'X-GitHub-Request-Id': 'AE83:3F40:2151C46:438A840:65C38178'}

The .headers attribute 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":

Language: Python
>>> response.headers["Content-Type"]
'application/json; charset=utf-8'

There’s something special about this dictionary-like headers object. The HTTP specification defines headers as case-insensitive, which means you can access them without worrying about their capitalization:

Language: Python
>>> 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, 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.

Add 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:

Language: Python Filename: search_popular_repos.py
import requests

response = requests.get(
    "https://api.github.com/search/repositories",
    params={"q": "language:python", "sort": "stars", "order": "desc"},
)

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']}\n")

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() either as a dictionary, as you’ve just done, or as a list of tuples:

Language: Python
>>> 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:

Language: Python
>>> requests.get(
...     "https://api.github.com/search/repositories",
...     params=b"q=language:python&sort=stars&order=desc",
... )
<Response [200]>

In the exercise below, you’ll use a query parameter to look up an author’s books from a small library API that sometimes goes down for maintenance, so your code has to handle both a normal reply and an error reply:

Loading exercise...

Query strings are useful for parameterizing GET requests. Another way to customize your requests is by adding or modifying the headers that you send.

Customize 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:

Language: Python Filename: 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"},
)

json_response = response.json()
first_repository = json_response["items"][0]
print(first_repository["text_matches"][0]["matches"])

The Accept header tells the server what content types your application can handle. In this case, since you’re expecting the matching search terms to be highlighted, you’re using the header value application/vnd.github.text-match+json. This is a proprietary GitHub Accept header where the content is a special JSON format.

When you run this Python script, you’ll get a result similar to the one shown below:

Language: Shell
$ python text_matches.py
[{'text': 'Real Python', 'indices': [23, 34]}]

Before you learn more ways to customize requests, you’ll broaden your horizons by exploring other HTTP methods.

Use Other HTTP Methods

Aside from GET, other popular HTTP methods include POST, PUT, DELETE, HEAD, PATCH, and OPTIONS. For each of these HTTP methods, Requests provides a function with a similar signature to get().

You’ll notice that Requests provides an intuitive interface for all the common HTTP methods:

Language: Python
>>> import requests

>>> requests.get("https://httpbin.org/get")
<Response [200]>
>>> requests.post("https://httpbin.org/post", data={"key": "value"})
<Response [200]>
>>> requests.put("https://httpbin.org/put", data={"key": "value"})
<Response [200]>
>>> requests.delete("https://httpbin.org/delete")
<Response [200]>
>>> requests.head("https://httpbin.org/get")
<Response [200]>
>>> requests.patch("https://httpbin.org/patch", data={"key": "value"})
<Response [200]>
>>> requests.options("https://httpbin.org/get")
<Response [200]>

In the example above, you called each function to make a request to the httpbin service using the corresponding HTTP method.

All of these functions are high-level shortcuts to requests.request(), which takes the method name as its first argument:

Language: Python
>>> requests.request("GET", "https://httpbin.org/get")
<Response [200]>

You could use the equivalent lower-level function call, but the power of Python’s Requests library is in its human-friendly, high-level interface. You can inspect the responses just like you did before:

Language: Python
>>> 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"]
{}

No matter which method you use, you get a Response object that provides access to headers, response bodies, status codes, and more.

Next, you’ll take a closer look at the POST, PUT, and PATCH methods and learn how they differ from the other request types.

Send Request Data

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. With Requests, you pass this payload to the corresponding function’s data parameter.

The data parameter 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