Making HTTP Requests with requests
The requests library is the de facto standard for making HTTP requests in Python. It provides a clean, intuitive API that handles the messy details of HTTP so you can focus on your application logic.
If you have worked with Python built-in urllib, requests will feel like a breath of fresh air. It handles connection pooling, Keep-Alive, and automatic JSON decoding with almost no setup.
Installing requests
Before using requests, install it via pip:
pip install requests
That is it. The library has no required dependencies beyond Python standard library.
Making Your First Request
The most common HTTP method is GET, used to fetch data from a server:
import requests
response = requests.get("https://api.github.com/users/octocat")
print(response.status_code) # 200
print(response.text) # Raw response body as string
The response object contains everything about the request. The status_code tells you whether the request succeeded. A 200 means OK, 404 means not found, and 5xx errors indicate server problems.
Working with Response Data
The requests library automatically decodes common response formats. For JSON responses, use the .json() method:
import requests
response = requests.get("https://api.github.com/users/octocat")
data = response.json()
print(data["login"]) # octocat
print(data["followers"]) # number of followers
If the response is not valid JSON, .json() raises an exception.
For other response types, access the raw content directly:
response = requests.get("https://example.com/image.png")
content = response.content # Raw bytes
# Save to file
with open("image.png", "wb") as f:
f.write(content)
Sending Data with POST
POST requests send data to the server, commonly used when submitting forms or creating resources:
import requests
payload = {"username": "alice", "password": "secret123"}
response = requests.post("https://httpbin.org/post", data=payload)
print(response.status_code) # 200
print(response.json()) # Echoes back what was sent
The data parameter sends form-encoded data. For JSON bodies, use json=:
import requests
payload = {"title": "My Post", "body": "Content here"}
response = requests.post("https://httpbin.org/post", json=payload)
print(response.status_code) # 200
Using json= automatically sets the Content-Type header to application/json.
Query Parameters
Append parameters to the URL using the params argument:
import requests
params = {"q": "python requests", "limit": 5}
response = requests.get("https://httpbin.org/get", params=params)
print(response.url) # https://httpbin.org/get?q=python+requests&limit=5
The library handles URL encoding automatically.
Custom Headers
Pass custom headers with the headers argument:
import requests
headers = {
"Authorization": "Bearer your-token-here",
"Accept": "application/json",
"User-Agent": "MyApp/1.0"
}
response = requests.get("https://api.example.com/data", headers=headers)
Common uses include authentication tokens, content type negotiation, and user agent strings.
Handling Errors Gracefully
HTTP errors are inevitable. The requests library provides ways to handle them:
import requests
response = requests.get("https://httpbin.org/status/404")
# Check status code manually
if response.status_code == 404:
print("Resource not found")
# Raise an exception for bad status codes
try:
response.raise_for_status()
except requests.HTTPError as e:
print(f"Request failed: {e}")
Using raise_for_status() is cleaner than checking status codes manually—it raises an exception for any 4xx or 5xx response.
Timeouts
Always set timeouts to prevent your program from hanging indefinitely:
import requests
# Timeout after 5 seconds
response = requests.get("https://httpbin.org/delay/10", timeout=5)
# Raises requests.exceptions.ReadTimeout
A timeout of 5-10 seconds is reasonable for most APIs. Adjust based on the service you are calling.
Session Objects
If you make multiple requests to the same server, use a Session to reuse connections:
import requests
session = requests.Session()
session.headers.update({"Authorization": "Bearer token"})
# Subsequent requests reuse the connection and headers
response1 = session.get("https://api.example.com/users")
response2 = session.get("https://api.example.com/posts")
Sessions also let you persist cookies across requests, which is useful for authentication flows.
Real-World Example: Fetching Weather Data
Here is a practical example using a public weather API:
import requests
import os
API_KEY = os.environ.get("WEATHER_API_KEY")
CITY = "London"
url = f"https://api.openweathermap.org/data/2.5/weather"
params = {"q": CITY, "appid": API_KEY, "units": "metric"}
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
print(f"Temperature: {data["main"]["temp"]}C")
print(f"Conditions: {data["weather"][0]["description"]}")
except requests.exceptions.Timeout:
print("Request timed out")
except requests.exceptions.HTTPError as e:
print(f"API error: {e}")
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
This pattern—timeout, raise_for_status(), JSON parsing, and exception handling—works well for most API integrations.
See Also
- http-module — Built-in HTTP client and server modules
- urllib-parse-module — URL parsing and manipulation
- json-module — JSON encoding and decoding