In questa lezione impari a
- capire che cos'è un'API e a cosa serve
- chiamare un servizio esterno con Python e la libreria requests
- leggere la risposta in formato JSON
- usare i parametri di una richiesta e il timeout
Che cos'è un'API?
Un'API è un modo concordato con cui un programma può chiedere informazioni o servizi a un altro programma. Non deve sapere come funziona il server: deve soltanto sapere che cosa chiedere e come leggere la risposta.
** Immagina una pizzeria.** Il menù è come l'API: ti dice che cosa puoi ordinare. Tu fai una richiesta, la cucina prepara il piatto e il cameriere ti porta la risposta. Non devi entrare in cucina per ottenere la pizza.
Per iniziare useremo il metodo GET, che serve a chiedere dati. Per ora non ci serve conoscere tutti gli altri metodi HTTP.
La prima API: una curiosità sui gatti
Prima di scrivere codice, apri questo indirizzo nel browser:
Vedrai una risposta simile a questa. La frase può cambiare ogni volta:
{
"fact": "Cats sleep for around 13 to 16 hours a day.",
"length": 46
}
Il sito ci ha restituito dei dati in formato JSON. Per ora pensa al JSON come a un testo organizzato in coppie: un nome e un valore.
Ora facciamo la stessa richiesta con Python
Per prima cosa, se necessario, installa la libreria requests dal terminale:
pip install requests
Poi esegui questo programma:
import requests
url = "https://catfact.ninja/fact"
risposta = requests.get(url, timeout=10)
dati = risposta.json()
print(dati["fact"])
Che cosa fanno queste righe?
import requestsimporta gli strumenti per contattare il sito.requests.get(url)invia la richiesta.risposta.json()trasforma la risposta JSON in dati Python.dati["fact"]prende il valore associato alla chiavefact.
Attenzione: Se compare un errore: controlla la connessione Internet e verifica di aver installato
requests. Il parametrotimeout=10evita di aspettare troppo se il server non risponde.
JSON: come troviamo il dato giusto?
Guarda questo esempio:
{
"nome": "Torino",
"temperatura": 18,
"piove": false
}
In Python, dopo aver convertito la risposta con .json(), possiamo leggere i valori usando il nome della chiave:
print(dati["nome"]) # Torino
print(dati["temperatura"]) # 18
print(dati["piove"]) # False
Le parentesi quadre non contengono il valore, ma il nome del campo che vogliamo leggere. Il nome deve corrispondere esattamente a quello presente nel JSON.
Piccola regola: se non sai quali campi contiene la risposta, prova prima con
print(dati). Osserva il risultato e soltanto dopo scegli quali valori stampare.
API per scoprire il mondo reale
Le API non servono soltanto per fare esempi. Possono fornire meteo, informazioni scientifiche e dati su tanti argomenti. Ecco quattro idee da esplorare, tutte senza chiave API.
- Meteo di Torino Open-Meteo, senza chiave API
- Terremoti in Italia INGV, dati sismici scientifici
- Immagine casuale di cane Dog CEO, risposta JSON breve
- Cerca Pinocchio Open Library, lista di risultati
Il nostro primo esempio con dati meteo
Questo programma chiede a Open-Meteo la temperatura attuale di Torino e la stampa. Non serve una chiave API.
import requests
url = "https://api.open-meteo.com/v1/forecast"
parametri = {
"latitude": 45.07,
"longitude": 7.69,
"current": "temperature_2m",
"timezone": "Europe/Rome"
}
risposta = requests.get(url, params=parametri, timeout=10)
dati = risposta.json()
print("Temperatura a Torino:",
dati["current"]["temperature_2m"], "°C")
Qui ci sono due idee nuove, ma semplici:
params=parametriinvia alla API i dettagli della richiesta, come latitudine e longitudine.dati["current"]["temperature_2m"]entra prima nel campocurrente poi legge la temperatura.
Osserva il risultato. Se oggi a Torino ci sono 18 °C, il programma stamperà un valore vicino a 18. Quando lo esegui in un altro momento, il valore può essere diverso.
Come viaggiano le richieste?
Quando un programma contatta una Web API, usa normalmente il protocollo HTTP o la sua versione sicura HTTPS. Non è necessario imparare tutti i dettagli subito: per ora ci basta distinguere questi concetti.
Quattro parole da conoscere
- Richiesta: il messaggio che il programma invia al servizio.
- Server: il computer o sistema che riceve la richiesta e prepara la risposta.
- Risposta: i dati o il messaggio che torna al programma.
- Codice di stato: un numero che aiuta a capire com'è andata la richiesta.
I codici di stato più comuni
200– la richiesta è andata a buon fine.400– la richiesta non è corretta.404– la risorsa richiesta non è stata trovata.429– sono state inviate troppe richieste in poco tempo.500– il server ha incontrato un problema.
In Python puoi controllare il codice con risposta.status_code:
print(risposta.status_code) # per esempio: 200
Da ricordare: ricevere una risposta non significa che contenga per forza il dato desiderato. Durante le prove stampa il contenuto completo con
print(risposta.text)oppureprint(risposta.json()).
Usare requests in modo ordinato
La libreria requests ci evita di costruire a mano i messaggi HTTP. Nei nostri primi programmi ci bastano tre passaggi: indicare l'indirizzo del servizio, inviare la richiesta con requests.get(), leggere la risposta per esempio con .json().
Che cosa sono i parametri?
A volte il servizio ha bisogno di dettagli aggiuntivi: una città, una parola da cercare o il numero di risultati da restituire. Questi dettagli si chiamano parametri.
import requests
url = "https://openlibrary.org/search.json"
parametri = {"q": "pinocchio", "limit": 3}
risposta = requests.get(url, params=parametri, timeout=10)
dati = risposta.json()
print(dati)
Il parametro q indica che cosa cercare, mentre limit limita il numero di risultati. Usare params è più sicuro e leggibile che costruire l'URL concatenando stringhe.
Perché scriviamo timeout=10?
timeout=10 indica a Python di non aspettare all'infinito se il server non risponde. È una buona abitudine anche nei programmi semplici e diventa ancora più importante quando useremo i thread.
Se qualcosa non funziona
Quando si lavora con Internet, qualche errore è normale. Prima di modificare il programma, controlla questi casi frequenti.
ModuleNotFoundError: probabilmenterequestsnon è installata. Nel terminale eseguipip install requests.requests.exceptions.ConnectionError: controlla la connessione e l'indirizzo scritto nel codice.Timeout: il server non ha risposto in tempo. Puoi riprovare più tardi.KeyError: stai cercando una chiave che non esiste nella risposta. Stampa prima tutto il JSON.IndexError: stai cercando un elemento in una lista vuota o a un indice non disponibile.
Una prima gestione degli errori
Non serve ancora imparare tutte le eccezioni. Comincia da questo modello, che mostra un messaggio se la richiesta non riesce:
import requests
url = "https://catfact.ninja/fact"
try:
risposta = requests.get(url, timeout=10)
risposta.raise_for_status()
dati = risposta.json()
print(dati["fact"])
except requests.exceptions.RequestException as errore:
print("Non riesco a contattare il servizio:", errore)
raise_for_status() segnala le risposte HTTP di errore. Il blocco try/except permette al programma di mostrare un messaggio comprensibile invece di interrompersi senza spiegazioni.
ConnectionError: quando la connessione non riesce
Quando una richiesta a un servizio esterno non funziona, Python e le librerie utilizzate possono segnalare il problema con un'eccezione. Per capire come gestirla, distinguiamo due casi.
1. requests.exceptions.ConnectionError: l'errore generato da requests
Quando usiamo la libreria requests, può essere la libreria stessa a sollevare requests.exceptions.ConnectionError, per esempio se non riesce a stabilire una connessione con il server. In questo caso non dobbiamo scrivere noi raise: l'eccezione viene generata durante la richiesta.
import requests
try:
risposta = requests.get("https://catfact.ninja/fact", timeout=10)
risposta.raise_for_status()
dati = risposta.json()
print(dati["fact"])
except requests.exceptions.ConnectionError:
print("Impossibile connettersi al servizio: controlla la rete.")
except requests.exceptions.Timeout:
print("Il servizio non ha risposto entro il tempo previsto.")
except requests.exceptions.RequestException as errore:
print("La richiesta non è riuscita:", errore)
Il primo except gestisce i problemi di connessione, il secondo i timeout e l'ultimo gli altri errori della libreria requests. RequestException è la classe base comune per queste eccezioni.
2. ConnectionError: sollevare un'eccezione nel proprio codice
Python dispone anche dell'eccezione standard ConnectionError. Possiamo sollevarla nel nostro codice quando rileviamo una condizione che vogliamo segnalare come problema di connessione.
Prima, vediamo la differenza tra stampare un messaggio e sollevare un'eccezione.
Con print(), il programma mostra un messaggio, ma non segnala un'eccezione:
def chiedi_dati(server_online):
if not server_online:
print("Il server non è raggiungibile")
return
return "Dati ricevuti"
risultato = chiedi_dati(False)
print("Il programma continua")
Con raise, invece, solleviamo un'eccezione. Il normale flusso della funzione si interrompe e Python cerca un blocco except in grado di gestirla:
def chiedi_dati(server_online):
if not server_online:
raise ConnectionError("Il server non è raggiungibile")
return "Dati ricevuti"
try:
risultato = chiedi_dati(False)
print(risultato)
except ConnectionError as errore:
print(f"Errore di connessione: {errore}")
print("Il programma continua")
Osserva i passaggi:
chiedi_dati(False)richiama la funzione passandoFalsecome argomento.- La condizione
if not server_onlinerisulta vera. raise ConnectionError("Il server non è raggiungibile")crea e solleva l'eccezione con quel messaggio.- Python interrompe il normale flusso del blocco
trye passa al bloccoexcept ConnectionError, che è in grado di gestire quel tipo di eccezione. as erroreassegna l'eccezione intercettata alla variabileerrore; scrivendo{errore}nella f-string ne visualizziamo il messaggio.- Dopo il blocco
except, il programma continua e stampaIl programma continua.
Perché usare raise invece di print()? Perché una funzione può segnalare al codice che l'ha chiamata che non è riuscita a completare il lavoro. Il codice chiamante può decidere come reagire usando try/except. Se l'eccezione non viene intercettata, il programma può terminare mostrando il traceback, cioè il rapporto tecnico dell'errore.
Attenzione: nell'esempio
server_onlineè un valore usato per simulare la situazione. Non verifica davvero se un server è raggiungibile.
Come ricordare la differenza
| Istruzione o tipo | Significato |
|---|---|
print("messaggio") |
Mostra un messaggio, senza sollevare un'eccezione. |
raise ConnectionError("messaggio") |
Solleva l'eccezione standard di Python. |
except ConnectionError as errore |
Intercetta un'eccezione di quel tipo e la rende disponibile nella variabile errore. |
requests.exceptions.ConnectionError |
Eccezione specifica che può essere sollevata dalla libreria requests durante un problema di connessione. |
requests.exceptions.RequestException |
Classe base per gli errori della libreria requests. |
Regola pratica: quando usi
requests, puoi gestirerequests.exceptions.ConnectionErroroppure, più in generale,requests.exceptions.RequestException. Quando scrivi una tua funzione e vuoi segnalare esplicitamente un problema di connessione, puoi sollevare l'eccezione standard conraise ConnectionError(...).
Collegamento con concorrenza e parallelismo: nei prossimi moduli useremo le eccezioni per gestire gli errori durante le richieste ai servizi esterni. Ricorda che un'eccezione non intercettata dentro un thread non viene automaticamente trasferita al thread principale: se serve, dovremo prevedere un modo per comunicare l'errore.
Adesso prova tu
Facile · 5 minuti
Esercizio 1 – Cambia la curiosità. Esegui il programma Cat Facts. Stampa l'intero dizionario con print(dati) e individua la chiave che contiene la curiosità.
Mostra la soluzione
import requests
risposta = requests.get("https://catfact.ninja/fact", timeout=10)
dati = risposta.json()
print(dati) # tutto il dizionario
print(dati["fact"]) # la curiosità
print(dati["length"]) # la lunghezza della frase
Facile · 5 minuti
Esercizio 2 – Cambia città. Copia il programma del meteo, cambia latitudine e longitudine con quelle di una città italiana a tua scelta, poi modifica il messaggio stampato per mostrare il nome della città scelta.
Mostra la soluzione
import requests
url = "https://api.open-meteo.com/v1/forecast"
parametri = {
"latitude": 41.89, # Roma
"longitude": 12.48,
"current": "temperature_2m",
"timezone": "Europe/Rome"
}
dati = requests.get(url, params=parametri, timeout=10).json()
print("Temperatura a Roma:", dati["current"]["temperature_2m"], "°C")
Intermedio · 10 minuti
Esercizio 3 – Leggi più libri. Apri nel browser la ricerca di Pinocchio su Open Library, osserva la risposta e individua il campo docs. Poi usa Python e un ciclo for per stampare il titolo di ogni libro trovato.
Mostra la soluzione
import requests
parametri = {"q": "pinocchio", "limit": 5}
dati = requests.get(
"https://openlibrary.org/search.json",
params=parametri, timeout=10
).json()
for libro in dati["docs"]:
print(libro["title"])
Sfida facoltativa
Esercizio 4 – Gestisci un problema di rete. Aggiungi al programma del meteo un blocco try/except e stampa un messaggio comprensibile se la richiesta non riesce. Non serve ancora gestire tutti i tipi di errore.
Mostra la soluzione
import requests
url = "https://api.open-meteo.com/v1/forecast"
parametri = {
"latitude": 45.07,
"longitude": 7.69,
"current": "temperature_2m"
}
try:
dati = requests.get(url, params=parametri, timeout=10).json()
print("Temperatura a Torino:",
dati["current"]["temperature_2m"], "°C")
except requests.exceptions.RequestException as errore:
print("Il servizio non risponde. Riprova più tardi.")
print("Dettaglio tecnico:", errore)
Da fare da solo
- Chiedi all'utente il nome di una città italiana, poi usa Open-Meteo per stamparne la temperatura attuale (cerca su Google "latitudine longitudine nome città").
- Usa Dog CEO per stampare l'URL di 3 immagini di cani diverse, una per riga.
- Usa Open Library per cercare un libro che ti piace e stampa titolo e anno di prima pubblicazione (
first_publish_year) dei primi 3 risultati.
Perché stiamo studiando le API? Perché un programma può recuperare dati da servizi esterni invece di contenere tutte le informazioni al suo interno. Nel periodo 1 chiederemo il meteo di diverse città e confronteremo le richieste eseguite una dopo l'altra con quelle eseguite usando i thread. Il prossimo modulo è Concorrenza e parallelismo.