Python: Lavorare con le API (requests)
Impara a usare la libreria requests di Python per effettuare chiamate GET e POST, inviare header, parametri di query, gestire risposte JSON e gli errori.
La libreria requests è il modo standard per effettuare chiamate HTTP da Python. Avvolge il modulo di basso livello urllib in un'API pulita, così recuperare una pagina web o chiamare una REST API richiede una riga di codice invece di dieci. Questo capitolo copre tutto ciò di cui hai bisogno: installare requests, effettuare chiamate GET e POST, inviare header e parametri di query, lavorare con le risposte JSON, caricare file, usare le sessioni e gestire gli errori in modo robusto.
Installazione
requests non fa parte della libreria standard, quindi la installi con pip:
pip install requestsSe stai lavorando all'interno di un ambiente virtuale (consigliato), attivalo prima in modo che il pacchetto sia circoscritto al tuo progetto. Dopo l'installazione, verifica che funzioni:
import requests
print(requests.__version__) # e.g. 2.32.3Effettuare una richiesta GET
requests.get() invia una richiesta HTTP GET e restituisce un oggetto Response. Questa è l'operazione più comune — utilizzata per recuperare dati da API, pagine web e file.
import requests
response = requests.get("https://jsonplaceholder.typicode.com/todos/1")
print(response.status_code) # 200
print(response.url) # https://jsonplaceholder.typicode.com/todos/1
print(response.text) # raw response body as a stringOutput atteso:
200
https://jsonplaceholder.typicode.com/todos/1
{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}Leggere la risposta come JSON
La maggior parte delle API moderne restituisce JSON. Chiama .json() sulla risposta invece di analizzare .text manualmente — internamente chiama json.loads() e restituisce un dict o una lista Python.
import requests
response = requests.get("https://jsonplaceholder.typicode.com/todos/1")
data = response.json()
print(data["title"]) # delectus aut autem
print(data["completed"]) # FalseConsulta il capitolo Python JSON per i dettagli su come gli oggetti Python vengono mappati ai tipi JSON.
Inviare parametri di query
I parametri di query sono le coppie chiave-valore dopo il ? in un URL, come ?q=python&page=2. Passali come dict all'argomento params — requests li codifica nell'URL e li aggiunge automaticamente.
import requests
params = {
"q": "python requests",
"page": 1,
"per_page": 5,
}
response = requests.get("https://httpbin.org/get", params=params)
# requests builds the full URL for you
print(response.url)
# https://httpbin.org/get?q=python+requests&page=1&per_page=5Usa sempre params= invece di costruire l'URL a mano — gestisce correttamente i caratteri speciali e la codifica.
Inviare header di richiesta
Gli header trasportano metadati: token di autenticazione, preferenze di tipo di contenuto, chiavi API e altro ancora. Passali come dict a headers=:
import requests
headers = {
"Accept": "application/json",
"Authorization": "Bearer my-api-token",
"User-Agent": "MyApp/1.0",
}
response = requests.get("https://httpbin.org/headers", headers=headers)
print(response.json())Header comuni che invierai:
| Header | Scopo |
|---|---|
Authorization | Token di autenticazione (Bearer, Basic, ecc.) |
Content-Type | Formato del corpo della richiesta (es. application/json) |
Accept | Formato che vuoi ricevere dal server |
User-Agent | Identifica il tuo client presso il server |
X-API-Key | Chiave API in un header personalizzato (varia in base al servizio) |
Effettuare una richiesta POST
requests.post() invia dati al server — usato per creare risorse, inviare moduli o chiamare azioni.
Inviare JSON
Passa un dict Python a json=. La libreria lo serializza e imposta automaticamente l'header Content-Type: application/json:
import requests
payload = {
"title": "Buy groceries",
"completed": False,
"userId": 1,
}
response = requests.post(
"https://jsonplaceholder.typicode.com/todos",
json=payload,
)
print(response.status_code) # 201 Created
print(response.json())Output atteso:
201
{'title': 'Buy groceries', 'completed': False, 'userId': 1, 'id': 201}Inviare dati di un modulo
Alcune API o moduli HTML si aspettano dati application/x-www-form-urlencoded. Usa data= invece di json=:
import requests
form_data = {
"username": "alice",
"password": "secret",
}
response = requests.post("https://httpbin.org/post", data=form_data)
print(response.status_code)Inviare file (upload multipart)
Per caricare un file, aprilo in modalità binaria e passalo tramite files=:
import requests
with open("report.pdf", "rb") as f:
response = requests.post(
"https://httpbin.org/post",
files={"file": f},
)
print(response.status_code)requests codifica l'upload come multipart/form-data, che è ciò che la maggior parte degli endpoint di caricamento file si aspetta.
Altri metodi HTTP
Le REST API usano diversi verbi HTTP per operazioni diverse. requests fornisce una funzione per ogni metodo:
import requests
base = "https://jsonplaceholder.typicode.com/todos/1"
# Update a resource (replace entirely)
response = requests.put(base, json={"title": "Updated", "completed": True, "userId": 1})
print(response.status_code) # 200
# Partial update
response = requests.patch(base, json={"completed": True})
print(response.status_code) # 200
# Delete a resource
response = requests.delete(base)
print(response.status_code) # 200Gestione degli errori
Controllare i codici di stato
Il codice di stato HTTP indica se la richiesta è riuscita. I gruppi più importanti:
| Intervallo | Significato |
|---|---|
| 2xx | Successo (200 OK, 201 Created, 204 No Content) |
| 3xx | Reindirizzamento (gestito automaticamente da requests) |
| 4xx | Errore del client (400 Bad Request, 401 Unauthorized, 404 Not Found) |
| 5xx | Errore del server (500 Internal Server Error, 503 Service Unavailable) |
raise_for_status()
Chiamare .raise_for_status() su una risposta solleva automaticamente un'eccezione HTTPError se il codice di stato è 4xx o 5xx. È il modo più pulito per fallire rapidamente in caso di risposte errate:
import requests
response = requests.get("https://jsonplaceholder.typicode.com/todos/99999")
try:
response.raise_for_status()
data = response.json()
print(data)
except requests.exceptions.HTTPError as err:
print(f"HTTP error: {err}")Senza raise_for_status(), una risposta 404 sembra un successo — il tuo codice legge un corpo di errore e lo elabora silenziosamente.
Gestire gli errori di rete
I fallimenti a livello di rete (errore di risoluzione DNS, connessione rifiutata, timeout) sollevano requests.exceptions.ConnectionError o requests.exceptions.Timeout. Catturali entrambi con la classe base requests.exceptions.RequestException:
import requests
try:
response = requests.get("https://api.example.com/data", timeout=5)
response.raise_for_status()
data = response.json()
except requests.exceptions.Timeout:
print("The request timed out — server took too long to respond.")
except requests.exceptions.ConnectionError:
print("Could not connect — check your network or the URL.")
except requests.exceptions.HTTPError as err:
print(f"HTTP error {response.status_code}: {err}")
except requests.exceptions.RequestException as err:
print(f"Unexpected error: {err}")Questo schema copre l'intera gerarchia delle eccezioni: timeout, connessione, errore HTTP e la classe base catch-all. Consulta Python try/except per un approfondimento sulla gestione delle eccezioni in Python.
Imposta sempre un timeout
Per impostazione predefinita requests attenderà all'infinito se il server non risponde mai. Passa sempre timeout= per evitare programmi che si bloccano:
# timeout=(connect_timeout, read_timeout) in seconds
response = requests.get("https://api.example.com/data", timeout=(3, 10))La forma tupla imposta separatamente il timeout di connessione e il timeout di lettura. Un timeout di lettura di 10 secondi significa "attendi fino a 10 secondi tra un byte e l'altro dopo che la connessione è stata stabilita."
Usare le sessioni
Un oggetto requests.Session mantiene le impostazioni — header, cookie, autenticazione — attraverso più richieste allo stesso host. Riutilizza anche la connessione TCP sottostante (connection pooling), che è più veloce rispetto alla creazione di una nuova connessione per ogni chiamata.
import requests
with requests.Session() as session:
# Set headers once — every request in this session will include them
session.headers.update({
"Authorization": "Bearer my-api-token",
"Accept": "application/json",
})
# All requests reuse the connection and headers
r1 = session.get("https://api.example.com/users")
r2 = session.get("https://api.example.com/posts")
r3 = session.post("https://api.example.com/todos", json={"title": "New"})
print(r1.status_code, r2.status_code, r3.status_code)Usa una sessione ogni volta che effettui più di una richiesta allo stesso server.
Ispezionare la risposta
L'oggetto Response espone tutto ciò che il server ha restituito:
import requests
response = requests.get("https://httpbin.org/get")
print(response.status_code) # 200
print(response.reason) # OK
print(response.headers) # dict of response headers
print(response.headers["Content-Type"]) # application/json
print(response.encoding) # utf-8
print(response.elapsed) # how long the request took
print(response.url) # final URL (after redirects)Per contenuto binario (immagini, PDF), usa response.content (restituisce bytes) invece di response.text:
import requests
response = requests.get("https://httpbin.org/image/png")
with open("image.png", "wb") as f:
f.write(response.content)Autenticazione
HTTP Basic Auth
Passa una tupla (username, password) a auth=:
import requests
response = requests.get(
"https://httpbin.org/basic-auth/alice/secret",
auth=("alice", "secret"),
)
print(response.status_code) # 200Bearer token (chiavi API)
La maggior parte delle API moderne usa un bearer token nell'header Authorization:
import requests
headers = {"Authorization": "Bearer eyJhbGciOiJIUzI1NiIs..."}
response = requests.get("https://api.example.com/me", headers=headers)Non inserire mai token hardcoded nei file sorgente. Caricali da variabili d'ambiente o da un gestore di segreti:
import os
import requests
token = os.environ["API_TOKEN"]
headers = {"Authorization": f"Bearer {token}"}
response = requests.get("https://api.example.com/me", headers=headers)Esempio pratico — API di GitHub
Questo esempio mostra un pattern completo: sessione, bearer auth, paginazione, gestione degli errori e analisi JSON:
import os
import requests
GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "")
BASE_URL = "https://api.github.com"
with requests.Session() as session:
session.headers.update({
"Authorization": f"Bearer {GITHUB_TOKEN}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
})
try:
# Fetch the first page of public repos for a user
response = session.get(
f"{BASE_URL}/users/torvalds/repos",
params={"per_page": 5, "sort": "updated"},
timeout=10,
)
response.raise_for_status()
repos = response.json()
for repo in repos:
print(f"{repo['name']:40s} ★ {repo['stargazers_count']}")
except requests.exceptions.HTTPError as err:
print(f"GitHub API error: {err}")
except requests.exceptions.RequestException as err:
print(f"Network error: {err}")Questo pattern — sessione con header condivisi, raise_for_status(), try/except circoscritto — è l'approccio pronto per la produzione per qualsiasi client API che scrivi.
Richieste concorrenti con asyncio
Per i programmi che chiamano molti endpoint contemporaneamente, la libreria sincrona requests si blocca su ogni chiamata. Passa ad aiohttp (l'equivalente asincrono) e combinalo con il modulo asyncio di Python:
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as response:
return await response.json()
async def main():
urls = [
"https://jsonplaceholder.typicode.com/todos/1",
"https://jsonplaceholder.typicode.com/todos/2",
"https://jsonplaceholder.typicode.com/todos/3",
]
async with aiohttp.ClientSession() as session:
results = await asyncio.gather(*[fetch(session, u) for u in urls])
for r in results:
print(r["title"])
asyncio.run(main())Consulta il capitolo Python asyncio per capire come funziona async/await prima di adottare questo pattern.
Riferimento rapido
| Operazione | Codice |
|---|---|
| Richiesta GET | requests.get(url) |
| GET con parametri | requests.get(url, params={"key": "val"}) |
| GET con header | requests.get(url, headers={"Authorization": "Bearer token"}) |
| POST corpo JSON | requests.post(url, json={"key": "val"}) |
| POST dati modulo | requests.post(url, data={"key": "val"}) |
| Caricamento file | requests.post(url, files={"file": open("f.pdf", "rb")}) |
| PUT / PATCH / DELETE | requests.put/patch/delete(url, json=...) |
| Controllo stato | response.status_code |
| Eccezione su 4xx/5xx | response.raise_for_status() |
| Analisi corpo JSON | response.json() |
| Corpo testo grezzo | response.text |
| Corpo bytes grezzo | response.content |
| Impostare timeout | requests.get(url, timeout=5) |
| Riutilizzare connessione | with requests.Session() as s: ... |
Capitoli correlati
- Python pip — installa
requestse gestisci le dipendenze del progetto - Python JSON — comprendi la codifica JSON che alimenta la maggior parte delle risposte API
- Python try/except — scrivi una gestione robusta delle eccezioni per le chiamate di rete
- Python Virtual Environments — isola le dipendenze del progetto
- Python asyncio — chiamate HTTP concorrenti senza blocchi