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=xxx per i dati da inviare in POST
# - headers=yyy 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 stampare
response.url: vedrete comparire qualcosa come
https://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 useremo:
response.textper visualizzare i risultati come stringhe;response.json()quando la risposta è un JSON generato a partire da una API (per l’interazione di due app senza reale intervento umano)response.contentquando 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'}
Notate le due chiavi diverse nella risposta, "form" e "json": httpbin.org distingue i dati arrivati come form-encoded da quelli arrivati come JSON.
Nella pratica, oggi la maggior parte delle API REST si aspetta json=.
Gestione degli errori
Una richiesta di rete può fallire in tanti modi: il server non risponde, ci mette troppo tempo, restituisce un errore,etc…
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}")
else:
print("Tutto bene!")
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.
Attenzione… senza timeout= uno script che interroga un server irraggiungibile può rimanere bloccato indefinitamente.
Impostare sempre un timeout è buona pratica, non un dettaglio opzionale.
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 di qualunque città città con una rapida ricerca sul web, 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
(prova coi parametri
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.
Esercizio req06: Header Inspector (GET vs HEAD)
Fate una richiesta GET e una HEAD alla stessa pagina (es. https://adjam.org). Per entrambe stampate: status code, Server, Content-Type e Content-Length, e la lunghezza reale di response.content. Confermate che il body della HEAD risponde con Content-Length: 0 e che il body della GET risponde con la lunghezza reale. Infine stampate response.request.headers per scoprire che User-Agent sta mandando requests al posto vostro.
Esercizio req07: Pokédex da terminale
Con la PokéAPI (https://pokeapi.co/api/v2/pokemon/NOME) scrivete un programma che chiede il nome di un Pokémon e stampa numero, altezza, peso, tipi
e le prime cinque mosse. Gestite il caso di nome sbagliato: la PokéAPI risponde 404, quindi decidete voi come accorgervene e cosa mostrare.
Attenzione: la risposta è un JSON annidato, con dizionari dentro liste dentro dizionari. Conviene stamparlo intero una volta per capirne la struttura.
Esercizio req08: Foto del giorno
L’API https://dog.ceo/api/breeds/image/random restituisce un JSON con l’URL di una foto casuale di cane. Scrivete uno script che:
- legge il JSON e ricava l’URL dell’immagine;
- fa una seconda richiesta a quell’URL;
- controlla l’header Content-Type e la dimensione con Content-Length;
- salva response.content in un file, aprendolo con “wb”.
Perché qui serve .content e non .text? Cosa succede se provate a salvare .text?
Esercizio req11: Dov’è la ISS?
L’API http://api.open-notify.org/iss-now.json restituisce la posizione attuale della Stazione Spaziale Internazionale.
Stampate latitudine e longitudine ogni 5 secondi, per un minuto, con time.sleep, e calcolate di quanti gradi si sposta la stazione.
Notate che l’URL usa http e non https: se la richiesta fallisce o viene reindirizzata, cosa ci dicono gli errori?
Usate anche http://api.open-notify.org/astros.json per stampare i nomi degli astronauti nello spazio in questo momento.