Prendere dati da internet, in poche righe
Prima o poi la maggior parte dei programmi Python reali deve comunicare con qualcosa in rete: una REST API, un servizio meteo, un endpoint di GitHub, un download. La libreria standard può farlo (tramite urllib), ma lo strumento di fatto nel mondo Python è una libreria di terze parti chiamata requests. La sua API è così tanto più amichevole che vale il singolo pip install.
pip install requests
Se non stai già lavorando dentro un ambiente virtuale, creane uno prima: mantiene questa installazione limitata al tuo progetto.
Una prima richiesta GET
Tre righe: chiama get, controlla il codice di stato, guarda il corpo. response.text è il corpo della risposta come stringa. Qui è troncato perché il JSON completo è lungo.
I codici di stato da riconoscere, a livello di promemoria:
- 200: OK, tutto ha funzionato.
- 201: Created (risposta comune a una POST).
- 301 / 302: reindirizzamenti; per impostazione predefinita
requestsli segue in automatico. - 400: richiesta non valida; qualcosa in quello che hai inviato era sbagliato.
- 401 / 403: non autenticato / non autorizzato.
- 404: la risorsa non esiste.
- 429: limite di richieste superato; rallenta.
- 500: errore del server.
Interpretare una risposta JSON
Quando l'endpoint restituisce JSON, chiama .json() sulla risposta: interpreta il corpo e ti restituisce un dict (o una lista):
Dietro le quinte, .json() equivale a json.loads(response.text): solo una scorciatoia per il caso più comune.
Inviare parametri di query
Non attaccare a mano ?key=value&... all'URL. Passa un dict a params=:
requests si occupa della codifica dell'URL al posto tuo: spazi, caratteri speciali e Unicode funzionano tutti in sicurezza.
L'URL effettivamente inviato è disponibile in response.url, comodo per il debug.
Richieste POST con corpo JSON
Per inviare dati (creare risorse, inviare moduli, chiamare endpoint che modificano qualcosa) usa requests.post:
import requests
payload = {
"title": "Docs update",
"body": "Added HTTP requests page.",
"labels": ["docs"],
}
response = requests.post(
"https://api.example.com/issues",
json=payload,
headers={"Authorization": "Bearer YOUR_TOKEN"},
)
print(response.status_code)
print(response.json())
L'argomento json= fa due cose: serializza payload in JSON e imposta Content-Type: application/json. Potresti fare entrambe le cose a mano con data=json.dumps(payload) e un header esplicito, ma json= è la scorciatoia idiomatica.
Per i dati codificati come modulo (quelli che invia un classico form HTML), usa invece data=:
requests.post("https://example.com/login", data={"user": "rosa", "password": "..."})
Header
Passa gli header personalizzati come dict:
import requests
response = requests.get(
"https://api.example.com/profile",
headers={
"Authorization": "Bearer abc123",
"User-Agent": "my-tool/1.0",
},
)
La maggior parte delle API vuole un header Authorization per l'autenticazione. Lo schema esatto (Bearer, Basic, Token) è indicato nella loro documentazione.
I timeout non sono facoltativi
Per impostazione predefinita, requests aspetta una risposta all'infinito. In un programma reale, questo trasforma un server instabile in uno script bloccato. Passa sempre un timeout:
import requests
try:
response = requests.get("https://api.example.com/slow", timeout=5)
except requests.Timeout:
print("Server took too long.")
Il numero è in secondi. timeout=5 significa "rinuncia se non abbiamo una risposta entro 5 secondi". Per un controllo più fine puoi passare una tupla (connect_timeout, read_timeout).
Gestione degli errori
Possono capitare due tipi di problemi:
- Errori a livello HTTP (4xx, 5xx): il server ha risposto, ma la risposta è un errore. Te lo dice
response.status_code. - Problemi a livello di rete: timeout, errori DNS, host irraggiungibili. Questi sollevano eccezioni.
Lo schema idiomatico li combina entrambi:
raise_for_status() non fa niente con le risposte 2xx e altrimenti solleva un'eccezione. RequestException è la classe base di ogni errore sollevato da requests: un'unica cattura per "qualsiasi cosa sia andata storta parlando con questo endpoint".
Scaricare un file
Per download binari grandi, leggi la risposta in streaming così non resta tutta in memoria:
import requests
url = "https://example.com/large.zip"
with requests.get(url, stream=True, timeout=30) as r:
r.raise_for_status()
with open("large.zip", "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
stream=True dice a requests di non precaricare il corpo. iter_content(chunk_size=...) restituisce il corpo un blocco alla volta, che scrivi direttamente su disco.
Sessioni: riusare connessioni e impostazioni
Se devi fare diverse richieste allo stesso servizio, usa una Session. Riusa la connessione TCP sottostante (più veloce) e ti permette di impostare i valori predefiniti una volta sola:
import requests
session = requests.Session()
session.headers.update({"Authorization": "Bearer abc123"})
# Ogni richiesta fatta con questa sessione porta con sé l'header.
a = session.get("https://api.example.com/users/1")
b = session.get("https://api.example.com/users/2")
c = session.post("https://api.example.com/users", json={"name": "Rosa"})
Per gli script che chiamano la stessa API decine di volte, una sessione dà un'accelerazione notevole.
Un esempio realistico: un piccolo client GitHub
Recuperare l'ultima release di un repository:
import requests
def latest_release(owner, repo):
url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
return {
"tag": data["tag_name"],
"name": data["name"],
"published": data["published_at"],
"url": data["html_url"],
}
release = latest_release("python", "cpython")
print(release)
Meno di quindici righe: costruisci l'URL, fai la richiesta, controlla gli errori, estrai i campi che ti interessano. È la forma della maggior parte dei client API che scriverai.
E urllib?
Il modulo urllib.request della libreria standard può fare tutto quello che fa requests, con più righe e meno comodità. Se proprio non puoi aggiungere una dipendenza, è lì:
import json
import urllib.request
with urllib.request.urlopen("https://api.github.com/repos/python/cpython") as r:
data = json.loads(r.read().decode("utf-8"))
print(data["name"])
Per qualsiasi cosa vada oltre uno script veloce, requests (o httpx se ti serve async) vale l'installazione.
Qualche buona abitudine
- Imposta sempre un timeout. Nessuna eccezione.
- Usa
raise_for_status()negli script che si aspettano un successo: trasforma una risposta sbagliata in un'eccezione ben visibile. - Passa dict a
params=ejson=, non stringhe costruite a mano. - Raggruppa le chiamate collegate in una
Sessionquando chiami molte volte la stessa API. - Registra
response.status_codeeresponse.textdurante il debug: di solito il corpo ti dice esattamente cosa non è piaciuto al server.
Prossimo argomento: date e orari
Con requests nella tua cassetta degli attrezzi puoi comunicare con qualsiasi API web moderna, scaricare file e costruire piccole integrazioni tra servizi. Insieme alle pagine precedenti su JSON e CSV, ora hai il ciclo completo "prendi i dati, leggili, fanne qualcosa, riscrivili": la forma di un numero enorme di script Python reali. Però la maggior parte di questi dati ha un timestamp, e la prossima pagina spiega come Python rappresenta date, orari e le trappole dei fusi orari da evitare.
Domande frequenti
Come faccio una richiesta HTTP in Python?
Installa la libreria requests con pip install requests, poi chiama requests.get(url) per una GET o requests.post(url, json=...) per una POST. L'oggetto risposta ha .status_code, .text, .json() e .headers. Esempio: r = requests.get('https://api.example.com/users/1').
Meglio requests o urllib?
requests per tutto quello che scriveresti a mano: la sua API è molto più amichevole. urllib è integrato e va bene quando aggiungere una dipendenza è impossibile, ma richiede più codice per cose come corpi JSON e sessioni. In produzione molti team usano anche httpx (una libreria compatibile con requests che supporta async).
Come invio JSON in una richiesta POST in Python?
Passa json={'key': 'value'} a requests.post(...). requests serializza il dict in JSON e imposta Content-Type: application/json per te. Non passare sia data= sia json=: scegline uno.
Come gestisco gli errori con la libreria requests?
Controlla response.status_code (200 significa successo), oppure chiama response.raise_for_status() per sollevare un'eccezione con i codici 4xx/5xx. I problemi a livello di rete (timeout, errori DNS) sollevano sottoclassi di requests.RequestException: cattura quella per coprire entrambi i casi.