Master Python Requests Over urllib: Don't be a Rookie!


Python

Author: Bashir Alam
Reviewer: Deepak Prasad

Introduction to Python requests Library

The Python programming language has become a powerful tool for various applications, from web development to data science. One area where Python shines is its ability to work with web services and APIs, and the requests library is arguably the most widely used tool for this purpose. This article aims to provide a comprehensive guide to the Python requests library, from installation to advanced use-cases.

 

What is the requests Library?

The requests library is a Python module that allows you to send all kinds of HTTP requests—GET, POST, PUT, DELETE, and more—in an extremely simple and intuitive manner. With just a few lines of code, you can retrieve web pages, submit forms, or interact with RESTful APIs. Developed by Kenneth Reitz, requests abstracts many of the complexities involved with sending HTTP requests, making it easier for Python developers to interact with the internet.

 

Why use requests over urllib?

While Python's Standard Library comes with the urllib module to handle HTTP requests, many find it cumbersome and less user-friendly. Here are some reasons why you might prefer requests over urllib:

  • Ease of Use: requests offers a more straightforward API for sending HTTP requests and takes care of many tasks automatically, making it easier to use.
  • JSON Support: The requests library provides built-in JSON support, making it easier to work with JSON APIs.
  • Session Handling: Managing sessions (a series of requests to the same server) is simplified, allowing you to persist cookies and headers across multiple requests.
  • Connection Timeouts: requests allows you to specify connection timeouts, giving you better control over your code's execution time.
  • Community Support: Given its popularity, you are more likely to find community-contributed plugins and tutorials that work specifically with the requests library, making it easier to find help when you need it.

 

Installation and Setup

Getting started with the requests library is remarkably easy, thanks to Python's package manager, pip. Below are the steps to install and verify the library.

To install the requests library, open your terminal or command prompt and run the following command:

pip install requests

If you're using Python 3.x and pip points to an older Python version, you may need to use pip3 instead:

pip3 install requests

Once the installation is complete, it's good practice to verify that the library is properly installed and accessible from your Python environment. To do this, open a Python interpreter by typing python or python3 in the terminal, and then try to import the requests module:

import requests

If you don't encounter any errors, then the installation was successful. You can also check the installed version of requests with the following code:

print(requests.__version__)

This will output the version of the requests library that is currently installed, e.g., 2.25.1.

 

Understanding the Basic Concepts

Before diving into the code and making HTTP requests with Python requests library, it's essential to understand some of the basic concepts surrounding HTTP methods, URLs, headers, and parameters.

 

HTTP methods: GET, POST, PUT, DELETE

HTTP methods determine the type of action you want to perform when making an HTTP request. The four primary methods you will most likely interact with when using the Python requests library are GET, POST, PUT, and DELETE.

GET: This method retrieves data from a specified resource.

response = requests.get('https://jsonplaceholder.typicode.com/todos/1')

POST: This method sends data to a server to create a resource.

payload = {'key1': 'value1', 'key2': 'value2'}
response = requests.post('https://httpbin.org/post', data=payload)

PUT: This method updates a current resource with new data.

payload = {'key1': 'new_value1', 'key2': 'new_value2'}
response = requests.put('https://httpbin.org/put', data=payload)

DELETE: This method removes a specified resource.

response = requests.delete('https://httpbin.org/delete')

 

Understanding URLs, Headers, and Parameters

URLs: The Uniform Resource Locator is the web address where your desired resource resides. The Python requests library functions take this as their primary argument.

url = 'https://jsonplaceholder.typicode.com/todos/1'
response = requests.get(url)

Headers: HTTP headers allow the client and the server to send additional information with the request or the response. For example, you can specify the type of response you want, like JSON.

headers = {'Accept': 'application/json'}
response = requests.get(url, headers=headers)

Parameters: These are extra data that you can send along with your GET request as query parameters in the URL.

payload = {'userId': 1}
response = requests.get('https://jsonplaceholder.typicode.com/todos', params=payload)

 

Making Your First Request

Once you have a grasp of the basic concepts, the next step is to start making HTTP requests. This section will guide you through sending your first GET and POST requests using Python requests library.

1. Sending a GET Request

A GET request is often used to retrieve data from a server. The following code shows how to send a simple GET request to an API endpoint that returns a TODO item as JSON:

import requests

# Define the URL for the GET request
url = 'https://jsonplaceholder.typicode.com/todos/1'

# Send the GET request
response = requests.get(url)

# Check if the request was successful
if response.status_code == 200:
    print("Success:", response.json())
else:
    print("Failed:", response.status_code)

# Output: Success: {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}

The Python requests.get() function makes a GET request to the URL specified and returns a Response object. You can inspect the object's properties like status_code to determine if the request was successful and json() to get the JSON content.

2. Sending a POST Request

POST requests are used to send data to a server, typically to create a new resource or submit form data. Below is a Python code example that sends a POST request:

# Define the URL and payload
url = 'https://httpbin.org/post'
payload = {'username': 'john', 'password': '12345'}

# Send the POST request
response = requests.post(url, json=payload)

# Check if the request was successful
if response.status_code == 200:
    print("Successfully posted data:", response.json())
else:
    print("Failed to post data:", response.status_code)

# Output could be like: Successfully posted data: {'args': {}, 'data': '', 'files': {}, 'form': {'username': 'john', 'password': '12345'}, 'headers': {...}, 'json': None, 'origin': '...', 'url': 'https://httpbin.org/post'}

Here, requests.post() sends data as JSON to the server. The Response object has the same properties as in the GET example.

 

Handling Responses

Once you've made a request, it's crucial to understand how to handle the responses that you receive. This section will go through understanding status codes, reading the response content, and parsing JSON responses.

1. Understanding Status Codes

HTTP status codes are three-digit numbers that indicate the result of your HTTP request. Commonly used status codes include 200 for success, 404 for not found, 400 for bad request, etc.

import requests

response = requests.get('https://jsonplaceholder.typicode.com/todos/1')

# Check the status code
status_code = response.status_code
if status_code == 200:
    print("Request was successful.")
elif status_code == 404:
    print("Resource not found.")
else:
    print(f"An error occurred. HTTP Status Code: {status_code}")

2. Reading Response Content

The content of the HTTP response can be read using various methods, like text to get the content as a string or content to get it as bytes.

# Get content as a string
text_content = response.text
print("Text Content:", text_content)

# Get content as bytes
byte_content = response.content
print("Byte Content:", byte_content)

3. Parsing JSON Responses

Often, APIs return responses in JSON format. The Python requests library makes it incredibly easy to parse JSON responses using the json() method on the Response object.

# Parse JSON response
json_content = response.json()
print("JSON Content:", json_content)

# Access individual fields
print("User ID:", json_content["userId"])
print("Title:", json_content["title"])

Here's an example that combines all three aspects:

# Make a GET request
response = requests.get('https://jsonplaceholder.typicode.com/todos/1')

# Check status code
if response.status_code == 200:
    print("Request was successful.")
    
    # Read and print text content
    print("Text Content:", response.text)
    
    # Parse and print JSON content
    json_content = response.json()
    print("JSON Content:", json_content)
    
    # Access and print individual fields
    print("User ID:", json_content["userId"])
    print("Title:", json_content["title"])
else:
    print(f"An error occurred. HTTP Status Code: {response.status_code}")

 

Advanced Features

As you get more comfortable with the basics of the Python requests library, you'll want to explore some of the more advanced functionalities it offers. This section will cover customizing request headers, handling cookies, dealing with redirects, and setting timeouts.

 

1. Customizing Request Headers

Sometimes, you'll need to include specific headers in your request, such as an API key or a custom User-Agent string. You can do this using the headers parameter.

# Define custom headers
headers = {'User-Agent': 'my-app', 'Authorization': 'Bearer <Your-API-Token>'}

# Make a GET request with custom headers
response = requests.get('https://some-api.com/data', headers=headers)

# Process response
if response.status_code == 200:
    print("Data retrieved successfully.")

 

2. Handling Cookies

The Python requests library can also send and receive cookies. Cookies are generally used for maintaining user sessions or tracking user behavior.

# Define a URL
url = 'https://some-website.com/login'

# Data that needs to be sent
payload = {'username': 'user', 'password': 'pass'}

# Make a POST request to set cookies
response = requests.post(url, data=payload)

# Extract cookies
cookies = response.cookies

# Make another request using the cookies
response = requests.get('https://some-website.com/dashboard', cookies=cookies)

 

3. Redirects and Following Links

By default, the Python requests library follows HTTP redirects. However, you can control this behavior using the allow_redirects parameter.

# Make a request without following redirects
response = requests.get('https://some-website.com/redirect', allow_redirects=False)

# Check if it's a redirect
if response.status_code == 302:
    print("This is a redirect. The new URL is:", response.headers['Location'])

 

4. Handling Timeouts

You can set timeouts to ensure that your request doesn't hang indefinitely. The timeout parameter can be used to specify how long the request should wait to connect and receive data.

# Make a GET request with a timeout of 5 seconds
try:
    response = requests.get('https://some-api.com/data', timeout=5)
except requests.exceptions.Timeout:
    print("The request timed out.")
else:
    if response.status_code == 200:
        print("Data retrieved successfully.")

# If the request takes longer than 5 seconds to connect or receive data, a Timeout exception will be raised.

 

Parameters and Payloads

Often, you'll need to send additional data along with your HTTP requests, whether it's search queries for a GET request or form data for a POST request. The Python requests library makes it easy to send such data using parameters and payloads.

 

1. Sending URL Parameters

URL parameters are typically sent in GET requests to filter or customize the data you're requesting. These are the key=value pairs you often see in URLs. You can send these using the params keyword argument.

# Define URL and parameters
url = 'https://some-api.com/search'
parameters = {'query': 'python', 'limit': 10}

# Send GET request with URL parameters
response = requests.get(url, params=parameters)

# Check response
if response.status_code == 200:
    print(f"Search results: {response.json()}")

 

2. Sending Payload in POST Requests

When you want to create or update resources on the server, you'll typically use a POST request with a payload. The payload usually contains the data you want to send, in the form of a dictionary, list, or JSON.

# Define URL and payload
url = 'https://some-api.com/create'
payload = {'name': 'John', 'age': 30}

# Send POST request with payload
response = requests.post(url, json=payload)

# Check response
if response.status_code == 201:
    print(f"Resource created: {response.json()}")

 

3. Uploading Files

Sometimes you'll need to upload files like images, PDFs, or text files to a server. You can do this using the files keyword argument in a POST request.

# Define URL and files to upload
url = 'https://some-api.com/upload'
files = {'file': ('myfile.txt', open('myfile.txt', 'rb'))}

# Send POST request with file
response = requests.post(url, files=files)

# Check response
if response.status_code == 200:
    print(f"File uploaded successfully: {response.json()}")

 

Sessions and Persistent Connections

HTTP is a stateless protocol, which means each request is independent and contains all the information needed for processing. However, many applications require a level of statefulness to track user data across multiple interactions. That's where HTTP sessions come in, and Python requests library provides an easy way to manage these sessions.

1. What is a Session?

An HTTP session is a sequence of network request-response transactions. In the context of the requests library, a session is an instance of the Session class. It allows you to persist certain parameters like headers and cookies across multiple requests, reducing redundant code and making your code more efficient.

2. Creating and Managing Sessions

Creating a session is simple; you instantiate the Session class and then use that session object for subsequent requests. Here's a basic example:

# Importing requests module
import requests

# Create a session
s = requests.Session()

# Add headers to the session
s.headers.update({'user-agent': 'my-app'})

# Perform a GET request
response = s.get('https://some-api.com/resource')

# Perform another GET request, this one will use the same headers as the first one
another_response = s.get('https://some-api.com/another-resource')

# Close the session
s.close()

In this example, we first create a session object and set its headers. Then, we perform two GET requests. Both will use the headers we set on the session object. This is useful if you're interacting with an API that requires authentication via headers, as you only need to set them once for the session.

 

Error Handling

Even with well-constructed API calls, you're likely to encounter errors at some point. These could be client errors (like 404 Not Found) or server errors (like 500 Internal Server Error). It's crucial to implement robust error-handling routines to manage these gracefully.

1. Handling HTTP Errors

The simplest way to check for HTTP errors is to use the response.raise_for_status() method, which will raise an HTTPError if the HTTP request returned an unsuccessful status code. Here's a basic example:

import requests

try:
    response = requests.get('https://some-api.com/resource')
    response.raise_for_status()  # Will raise an HTTPError if the HTTP request returned an unsuccessful status code
except requests.HTTPError as err:
    print(f"An HTTP error occurred: {err}")

2. Raising Custom Exceptions

Sometimes, you'll want to raise your own exceptions based on the content of the response or some other condition. This is useful when dealing with APIs that return a 200 OK status code but include the actual error message in the response body.

response = requests.get('https://some-api.com/resource')
data = response.json()

if "error" in data:
    raise ValueError(f"API returned an error: {data['error']}")

3. Debugging Failed Requests

When things go wrong, you'll want to gather as much information as possible to debug the issue. Python requests library provides multiple ways to do this:

Response Content: The first thing to check is the content of the response object using response.content or response.text.

print(response.content)

Response Headers: Sometimes the headers contain useful debugging information. These can be accessed with response.headers.

print(response.headers)

Logs: For even more details, you can enable logging in Python to get information on what requests is doing under the hood.

import logging

logging.basicConfig(level=logging.DEBUG)

 

Authentication

A common requirement for interacting with web services is authentication. The Python requests library provides built-in mechanisms for several types of authentication, as well as the flexibility to handle any custom schemes you might encounter.

1. Basic Authentication

Basic authentication is one of the oldest and most straightforward methods of HTTP authentication. The Python requests library makes basic authentication easy.

Example:

import requests
from requests.auth import HTTPBasicAuth

response = requests.get('https://api.example.com/resource', auth=HTTPBasicAuth('username', 'password'))

# Alternatively, you can use a simpler syntax
response = requests.get('https://api.example.com/resource', auth=('username', 'password'))

2. OAuth

OAuth (Open Authorization) is a more modern authentication scheme and is commonly used for token-based authentication. While requests itself doesn't provide built-in OAuth support, you can use an OAuth library or manually set the Authorization header.

Example using manually set headers:

headers = {'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}
response = requests.get('https://api.example.com/resource', headers=headers)

3. Custom Authentication

Sometimes, you may need to implement a custom authentication mechanism that isn't supported out of the box by requests. In such cases, you can subclass requests.auth.AuthBase to create your custom authentication scheme.

Example:

from requests.auth import AuthBase

class CustomAuth(AuthBase):
    def __call__(self, request):
        # Implement your custom authentication here
        request.headers['X-Custom-Auth'] = 'my-auth-token'
        return request

# Usage
auth_instance = CustomAuth()
response = requests.get('https://api.example.com/resource', auth=auth_instance)

In this example, we define a custom authentication class CustomAuth that sets a header X-Custom-Auth. We then pass an instance of this class to the requests.get() method via the auth parameter.

 

Advanced Use-cases

After mastering the basics, you can leverage the Python requests library for more advanced scenarios like web scraping, asynchronous requests, and streaming large files or datasets.

1. Web Scraping with requests

Web scraping involves downloading web pages and extracting information from them. Below is a simple example where we scrape the title of a webpage using requests and BeautifulSoup.

import requests
from bs4 import BeautifulSoup

response = requests.get('https://example.com')
soup = BeautifulSoup(response.text, 'html.parser')

title_tag = soup.find('title')
print(f'Title of the web page is: {title_tag.string if title_tag else "not found"}')

2. Asynchronous Requests

While Python requests itself is synchronous, you can achieve asynchronous behavior by using Python's asyncio library along with aiohttp.

import aiohttp
import asyncio

async def fetch_url(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

loop = asyncio.get_event_loop()
result = loop.run_until_complete(fetch_url('https://example.com'))

3. Streaming Large Files or Datasets

Downloading large files or datasets can be efficiently done using requests by streaming the data.

response = requests.get('https://example.com/large-file', stream=True)

with open('large_file', 'wb') as f:
    for chunk in response.iter_content(chunk_size=8192):
        f.write(chunk)

In this example, we set stream=True to ensure that only a chunk of the file is fetched at a time into memory. We then save these chunks into a file called large_file.

 

Performance Considerations

When working on projects that involve a high volume of HTTP requests, it's essential to consider performance aspects like speed and memory usage. Below are some points on how to measure and optimize performance.

1. Benchmarking requests vs Other Libraries

You can compare the performance of Python requests with other HTTP libraries using Python's built-in timeit module or similar benchmarking tools. Here's a simple example to compare requests and http.client.

import requests
import http.client
import timeit

def fetch_with_requests():
    requests.get('https://www.example.com')

def fetch_with_http_client():
    conn = http.client.HTTPSConnection("www.example.com")
    conn.request("GET", "/")
    conn.getresponse()

print("Requests Library:", timeit.timeit(fetch_with_requests, number=100))
print("HTTP.client:", timeit.timeit(fetch_with_http_client, number=100))

Sample Output:

Requests Library: 2.123456
HTTP.client: 1.789012

This sample output indicates that http.client is a bit faster for this specific task, although Python requests offers many more features and is easier to use.

2. Optimizing Your Requests

requests provides several features that allow you to optimize your HTTP requests for better performance.

Using Sessions for Multiple Requests

For making several Python requests to the same host, using a Session object can improve performance by reusing the underlying TCP connection.

with requests.Session() as session:
    session.get('https://www.example.com/page1')
    session.get('https://www.example.com/page2')

Limiting Redirection

By limiting redirection, you can avoid unnecessary HTTP requests.

response = requests.get('https://www.example.com', allow_redirects=False)

 

Comparison with Other Libraries

Comparing Python requests with other libraries like urllib, http.client, and third-party options can be a useful exercise to understand their relative strengths and weaknesses. Whether to use a table or a descriptive approach largely depends on the complexity and variety of the features you're comparing.

Feature requests urllib http.client Third-party Libraries
Ease of Use High Medium Low Varies
Redirect Handling Built-in Manual Manual Varies
Session Support Yes No No Varies
Timeout Handling Built-in Manual Built-in Varies
Custom Headers Easy Moderate Difficult Varies
Cookie Handling Built-in Manual Manual Varies
Community Support High High Medium Varies

Here is a summary of the comparison:

  • Ease of Use: requests shines in ease of use with simple syntax and methods for all kinds of HTTP actions. urllib, while comprehensive, often requires more code for the same tasks. http.client is a lower-level library that offers less abstraction, making it more cumbersome for typical tasks.
  • Session Support: requests provides a built-in Session class, making it simple to persist certain parameters across multiple requests. Neither urllib nor http.client offer this feature out of the box.
  • Custom Headers and Redirects: All libraries allow custom headers, but Python requests makes this particularly easy. It also handles redirects automatically, while in urllib and http.client, you would typically have to manage this yourself.

 

Frequently Asked Questions

Why is the requests library preferred over urllib?

The requests library is often preferred due to its user-friendly syntax and built-in methods for various HTTP actions, which makes it more accessible and easier to use than urllib.

What do I do when I encounter a 404 error?

A 404 error indicates that the resource you're trying to access doesn't exist. Double-check the URL and any parameters you've added to the request.

How do I send data or parameters with my request?

Data or parameters can be sent in the request's body or as URL parameters. The choice between the two largely depends on the API's requirements.

Is it possible to make asynchronous requests with requests?

While the native requests library doesn't support asynchronous operations, you can use it in conjunction with Python's asyncio or use an alternative library like httpx for asynchronous requests.

How can I improve the speed of my requests?

Consider using a session to persist parameters and reuse HTTP connections. You can also adjust timeout settings and explore parallel requests for more speed.

What are the security concerns when using requests?

Always be cautious with sending sensitive information such as passwords. Use HTTPS whenever possible and consider additional layers of security like OAuth for authentication.

How do I handle cookies with requests?

The requests library makes it easy to send and receive cookies through the cookies attribute of a response object or by using a session.

Can I integrate requests with my web scraping projects?

Absolutely. requests is often used in conjunction with web scraping libraries like BeautifulSoup to fetch web pages for parsing.

How do I debug a failed request?

You can inspect the response object to check the status code, headers, and other meta-information to help diagnose what might have gone wrong.

Can I use requests for testing APIs?

Yes, requests is commonly used in API testing, often in combination with testing frameworks like pytest.

 

Summary

In this comprehensive guide, we've walked you through the fundamentals and advanced features of the Python requests library. Starting with installation and basic HTTP methods, we delved into more complex topics like custom headers, cookies, and even asynchronous requests.

Core Concepts:

  • HTTP Methods: GET, POST, PUT, and DELETE are the primary HTTP methods you'll need to know.
  • Handling Responses: Knowing how to handle status codes and read response content is crucial for effective API interactions.
  • Advanced Features: From customizing request headers to managing sessions and cookies, requests offers a wide array of functionalities.
  • Error Handling: Properly catching and handling errors can save a lot of debugging time and make your application more robust.
  • Authentication: requests supports multiple methods of authentication, including Basic Auth and OAuth.
  • Performance: While requests is generally fast and efficient, you have options for optimizing performance, like reusing sessions.

Key Takeaways:

  • The Python requests library is an indispensable tool for any Python developer working with HTTP requests or APIs.
  • It's highly customizable, allowing you to tweak almost every aspect of your request process.
  • Proper error handling is essential for building reliable apps.
  • For specialized needs, requests works well with other Python libraries and frameworks, proving its flexibility.

 

Additional Resources

For further information and advanced topics, please refer to the Official Documentation.

Thank you for reading, and we hope this guide serves as a valuable resource on your coding journey!

 

Views: 50
Bashir Alam

Bashir Alam

He is a Computer Science graduate from the University of Central Asia, currently employed as a full-time Machine Learning Engineer at uExel. His expertise lies in Python, Java, Machine Learning, OCR, text extraction, data preprocessing, and predictive models. You can connect with him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment