Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 da requests in 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&current=temperature_2m...

Costruire questa stringa a mano, concatenando & e =, è esattamente il tipo di lavoro noioso e pieno di errori da cui una libreria come requests ci 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.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. 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: con timeout=2 l’eccezione Timeout scatterà 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:

  1. chiede all’utente il nome di una città;
  2. usa il geocoding di Open-Meteo per ottenere latitudine e longitudine;
  3. usa quei dati per interrogare le previsioni per i prossimi 3 giorni (suggerimento: parametro daily=temperature_2m_max,temperature_2m_min);
  4. stampa un piccolo riepilogo giorno per giorno;
  5. gestisce in modo appropriato ogni possibile errore (città non trovata, timeout, errore di connessione), senza mai far crashare il programma.