Python requests
Il venerabile modulo requests è lo standard de facto in Python per realizzare, gestire, analizzare richieste e risposte HTTP.
Questa libreria non fa parte della dotazione ufficiale standard e va quindi installata, tramite l’interfaccia di Thonny o tramite il
comando:
pip install requests
Warning
Il protocollo HTTP!!!
Se siete qui a studiare il modulo
requests, si presuppone abbiate già una discreta conoscenza del protocollo HTTP.
La sintassi di base del modulo è strutturata nel modo seguente:
import requests
# method è una funzione generica per uno dei metodi HTTP: get, post, put, head ... etc...
response = requests.method( URL , options )
# options è un contenitore generico per varie opzioni:
# - data=json_data per i dati da inviare in POST
# - headers=dict_headers per il dizionario degli headers da aggiungere alla richiesta
#
# response.status_code contiene lo status code della risposta
# response.text contiene il corpo della risposta come stringa
# response.content contiene il corpo della risposta come bytes
Richieste GET
Il metodo GET è quello che usiamo quando vogliamo ottenere dati da un server, senza modificarne lo stato.
import requests
url = "https://api.open-meteo.com/v1/forecast"
params = {
"latitude": 43.6,
"longitude": 13.5,
"current": "temperature_2m,relative_humidity_2m,wind_speed_10m"
}
response = requests.get(url, params=params)
print(response.status_code) # 200 se la richiesta è andata a buon fine
print(response.url) # l'URL completo, costruito automaticamente da params
Note
Il dizionario passato con
params=viene automaticamente trasformato darequestsin una query string. Provate a stampareresponse.url: vedrete comparire qualcosa comehttps://api.open-meteo.com/v1/forecast?latitude=43.6&longitude=13.5¤t=temperature_2m...Costruire questa stringa a mano, concatenando
&e=, è esattamente il tipo di lavoro noioso e pieno di errori da cui una libreria comerequestsci libera.
Il corpo della risposta si può leggere in diversi modi:
response.text # corpo come stringa grezza
response.content # corpo come bytes
response.json() # corpo interpretato come JSON e convertito in dict/list Python
Sostanzialmente andremo ad usare respose.text nei nostri script per visualizzare i risultati come stringhe; useremo response.json() quando
la risposta è un JSON generato a partire da una API (per l’interazione di due app senza reale intervento umano) e response.content quando la risposta è in formato binario (immagini, file, ecc.).
dati = response.json()
print(dati["current"]["temperature_2m"])
Warning
.json()solleva un’eccezione se il corpo della risposta non è JSON valido (ad esempio se il server ha risposto con una pagina di errore HTML). Vedremo tra poco come gestire questi casi.
Un esempio pratico: temperature a confronto
Mettiamo insieme quanto visto per scrivere qualcosa di utile: un piccolo script che confronta la temperatura attuale di alcune città.
import requests
url = "https://api.open-meteo.com/v1/forecast"
città = {
"Ancona": (43.6, 13.5),
"Milano": (45.46, 9.19),
"Roma": (41.9, 12.5)
}
for nome, (lat, lon) in città.items():
params = {"latitude": lat, "longitude": lon, "current": "temperature_2m"}
dati = requests.get(url, params=params).json()
temperatura = dati["current"]["temperature_2m"]
print(f"{nome}: {temperatura}°C")
Notate che il servizio Open-Meteo usato in questo esempio non richiede alcuna registrazione né API key: è comodo proprio per questo, oltre al fatto di restituire dati reali e verificabili.
Richieste POST
Il metodo POST si usa quando vogliamo inviare dati al server, tipicamente per creare qualcosa (un nuovo utente, un nuovo post, un ordine…).
requests offre due modalità per inviare dati con una POST:
import requests
# data=: invia i dati come form-encoded, esattamente come farebbe
# un <form> HTML inviato con method="post"
r1 = requests.post("https://httpbin.org/post", data={"nome": "Andrea", "corso": "Reti"})
# json=: serializza automaticamente il dizionario in JSON
# e imposta da solo l'header Content-Type: application/json
r2 = requests.post("https://httpbin.org/post", json={"nome": "Andrea", "corso": "Reti"})
Tip
httpbin.orgè un servizio pensato apposta per fare pratica con le richieste HTTP: qualsiasi cosa gli mandiate, ve la restituisce indietro dentro la risposta, così potete vedere esattamente cosa è arrivato al server.
print(r1.json()["form"]) # {'nome': 'Andrea', 'corso': 'Reti'}
print(r2.json()["json"]) # {'nome': 'Andrea', 'corso': 'Reti'}
Note
Notate le due chiavi diverse nella risposta,
"form"e"json":httpbin.orgdistingue i dati arrivati come form-encoded da quelli arrivati come JSON. Nella pratica, oggi la maggior parte delle API REST si aspettajson=.
Gestione degli errori
Una richiesta di rete può fallire in tanti modi: il server non risponde, ci mette troppo tempo, restituisce un errore. requests non solleva eccezioni per gli status code di errore (tipo 404 o 500) a meno che non lo chiediate esplicitamente con raise_for_status().
import requests
try:
response = requests.get("https://httpbin.org/delay/5", timeout=2)
response.raise_for_status()
except requests.exceptions.Timeout:
print("Il server ci ha messo troppo tempo a rispondere")
except requests.exceptions.HTTPError as e:
print(f"Errore HTTP: {e}")
except requests.exceptions.RequestException as e:
print(f"Qualcosa è andato storto: {e}")
Warning
Senza
timeout=, uno script che interroga un server irraggiungibile può rimanere bloccato indefinitamente. Impostare sempre un timeout è buona pratica, non un dettaglio opzionale.
Tip
https://httpbin.org/delay/5è un endpoint che risponde apposta dopo 5 secondi: contimeout=2l’eccezioneTimeoutscatterà sempre, permettendovi di vederla “dal vivo” senza dover simulare un guasto di rete vero.
Esercizi
Esercizio req01
Scrivete uno script che interroghi l’API di Open-Meteo per la vostra città e stampi temperatura, umidità e velocità del vento attuali, in un formato leggibile tipo:
Meteo a Senigallia:
Temperatura: 28.4°C
Umidità: 61%
Vento: 12.3 km/h
Tip
Potete trovare latitudine e longitudine della vostra città con una rapida ricerca, oppure usare il servizio di geocoding di Open-Meteo (
https://geocoding-api.open-meteo.com/v1/search?name=NOMECITTÀ).
Esercizio req02
Modificate lo script dell’esercizio precedente in modo che la città venga chiesta
all’utente con input(), invece di essere scritta nel codice. Gestite il caso in
cui la città non venga trovata dal servizio di geocoding (la risposta JSON in tal
caso non conterrà risultati: come ve ne accorgete?).
Esercizio req03
Scrivete uno script che invii una richiesta POST a https://httpbin.org/post
con un dizionario contenente il vostro nome, cognome e classe, usando json=.
Stampate poi a schermo l’intera risposta con response.json() e individuate,
dentro il JSON restituito, il punto esatto in cui ritrovate i dati che avete inviato.
Esercizio req04
Scrivete uno script che tenti una richiesta GET verso un indirizzo che non esiste
(es. https://questositosicuramentenonesiste12345.com), gestendo correttamente
l’eccezione con un messaggio d’errore chiaro per l’utente, invece di far crashare
il programma. Ripetete poi l’esercizio usando https://httpbin.org/delay/10 con
un timeout breve, per osservare la differenza tra un errore di connessione e
un timeout.
Esercizio req05
Scrivete un piccolo programma “stazione meteo da terminale” che:
- chiede all’utente il nome di una città;
- usa il geocoding di Open-Meteo per ottenere latitudine e longitudine;
- usa quei dati per interrogare le previsioni per i prossimi 3 giorni
(suggerimento: parametro
daily=temperature_2m_max,temperature_2m_min); - stampa un piccolo riepilogo giorno per giorno;
- gestisce in modo appropriato ogni possibile errore (città non trovata, timeout, errore di connessione), senza mai far crashare il programma.