Che cos’è un’API e cosa significa estrarne i dati
API (Application Programming Interface) — è l’interfaccia attraverso la quale un programma comunica con un altro e ottiene dati o avvia azioni senza conoscerne il funzionamento interno. In pratica, «lavorare con un’API» significa quasi sempre consumare un’API web tramite il protocollo HTTP: il client invia una richiesta a un indirizzo preciso (l’endpoint) e il server restituisce una risposta.
Quando si parla di estrazione di dati via API si intende l’intero ciclo di ottenimento e parsing dei dati di un servizio esterno:
- Costruire e inviare una richiesta HTTP corretta.
- Ricevere la risposta del server.
- Verificare il codice di stato e gli header.
- Fare il parsing del corpo della risposta (quasi sempre JSON) convertendolo in oggetti del programma.
- Gestire errori, nuovi tentativi e paginazione.
Vedi anche: l’articolo «Parsing di JSON» è dedicato al passaggio specifico del parsing dei dati, cioè alla conversione di una stringa JSON in oggetti. Questo articolo copre invece l’intero processo di lavoro con un’API, di cui quel parsing è solo una parte.
A differenza del web scraping dell’HTML, il lavoro con un’API si basa su risposte strutturate in un formato leggibile dalle macchine: per questo è più affidabile, più stabile e quasi sempre preferibile quando il servizio dispone di un’API ufficiale.
Anatomia di una richiesta HTTP
Ogni richiesta a un’API web è composta da diverse parti.
Metodo (verbo HTTP)
Il metodo descrive l’intenzione della richiesta:
- GET: ottenere dati (non modifica lo stato).
- POST: creare una nuova risorsa o inviare dati.
- PUT / PATCH: aggiornare una risorsa (per intero / in parte).
- DELETE: eliminare una risorsa.
URL e parametri di query
L’indirizzo dell’endpoint può contenere parametri di query per filtrare, ordinare e paginare:
https://api.example.com/users?role=admin&page=2&limit=50Header
Gli header trasportano i metadati della richiesta. I più importanti quando si lavora con un’API:
Authorization: dati di autenticazione (token, chiave).Content-Type: formato del corpo inviato (per esempioapplication/json).Accept: formato in cui il client desidera ricevere la risposta.User-Agent: identificativo del client.
Corpo della richiesta (body)
Con POST/PUT/PATCH i dati viaggiano nel corpo — di norma come stringa JSON, che va prima serializzata a partire dagli oggetti del programma.
Anatomia della risposta e il suo parsing
La risposta del server è composta da codice di stato, header e corpo.
Codici di stato
Prima di fare il parsing del corpo bisogna verificare il codice di stato:
- 2xx: successo (
200 OK,201 Created,204 No Content). - 3xx — reindirizzamento.
- 4xx — errore lato client (
400richiesta non valida,401non autenticato,403accesso negato,404non trovato,429troppe richieste). - 5xx — errore lato server.
Ha senso interpretare il corpo come dati validi solo con i codici 2xx. Anche il corpo di un errore, però, contiene spesso un JSON utile con la descrizione del problema.
Header della risposta
Dagli header della risposta si ricavano informazioni importanti: Content-Type (formato del corpo), parametri di paginazione, limiti di richieste (X-RateLimit-Remaining), direttive di cache.
Corpo della risposta
Il corpo sono i dati veri e propri. Nella maggior parte delle API è JSON, di cui va fatto il parsing (vedi «Parsing di JSON»). Meno spesso si incontrano altri formati testuali — XML (vedi «Parsing di XML») e CSV (vedi «Parsing di CSV») —, oltre a formati binari.
Autenticazione e autorizzazione
La maggior parte delle API richiede di dimostrare che il client ha diritto di accesso. I metodi più diffusi:
- Chiave API — una semplice stringa, trasmessa in un header o in un parametro della richiesta.
- Token Bearer / OAuth 2.0 — un token nell’header
Authorization: Bearer <token>; l’approccio più comune nelle API moderne. - Basic Auth — nome utente e password in forma codificata.
- HMAC / firma della richiesta — la richiesta viene firmata con un segreto; si usa nelle API di pagamento e cloud.
Importante: chiavi e token sono segreti. Non vanno tenuti nel codice né committati nel repository: usa variabili d’ambiente o archivi protetti.
Le difficoltà nel lavorare con le API
L’estrazione di dati via API raramente si riduce a una singola richiesta. Ecco le difficoltà tipiche a cui conviene prepararsi.
Paginazione
Un’API non restituisce quasi mai le collezioni grandi in un colpo solo: i dati vengono suddivisi in pagine. I modelli principali:
- Offset/limit —
?page=2&limit=50oppure?offset=100&limit=50. - A cursore (cursor) — la risposta include un puntatore alla pagina successiva (
next_cursor), da passare nella richiesta seguente. - Keyset — la pagina successiva si richiede in base al valore dell’ultimo elemento (per esempio l’
ido la data).
Per raccogliere tutti i dati serve un ciclo che scorra le pagine finché non si esauriscono.
Limite di frequenza delle richieste (rate limiting)
I servizi limitano il numero di richieste per unità di tempo. Superata la soglia, arriva il codice 429. La soluzione corretta è monitorare gli header dei limiti e applicare un’attesa esponenziale (backoff) con nuovi tentativi.
Rete inaffidabile e nuovi tentativi
Le richieste di rete possono fallire per timeout o per errori temporanei 5xx. Per aumentare la robustezza si applicano:
- timeout ragionevoli;
- nuovi tentativi (retry) per le richieste idempotenti, con attese crescenti;
- il pattern circuit breaker in caso di guasti sistematici.
Dati che cambiano e validazione
La risposta di un’API può arrivare incompleta, con campi null o con la struttura modificata dopo un aggiornamento di versione. Mai fidarsi ciecamente della struttura della risposta: i campi vanno controllati e i dati validati.
Versioning
Le API evolvono e le loro versioni cambiano (/v1/, /v2/). Conviene fissare una versione precisa e seguire gli annunci di deprecazione (deprecation).
Strutture annidate e formati diversi
I dati utili sono spesso nascosti in profondità in una struttura annidata (data.items[0].attributes.name). A volte, al posto del JSON, arriva XML (vedi «Parsing di XML») o CSV (vedi «Parsing di CSV»), il che richiede un parser diverso.
Esempi di implementazione in vari linguaggi
Di seguito, esempi minimi del ciclo completo: richiesta all’API, verifica dello stato e parsing della risposta JSON. Per uniformità si usa l’endpoint fittizio https://api.example.com/users/42.
Python
La popolare libreria requests si occupa sia della richiesta sia del parsing del JSON.
import requests
url = "https://api.example.com/users/42"
headers = {"Authorization": "Bearer YOUR_TOKEN"}
response = requests.get(url, headers=headers, timeout=10)
# Controllo dello stato
response.raise_for_status() # solleva un'eccezione con 4xx/5xx
# Parsing della risposta JSON in un dizionario
user = response.json()
print(user["name"])Esempio di raccolta di tutte le pagine (offset/limit):
def fetch_all_users():
users, page = [], 1
while True:
resp = requests.get(
"https://api.example.com/users",
params={"page": page, "limit": 50},
timeout=10,
)
resp.raise_for_status()
batch = resp.json()["data"]
if not batch:
break
users.extend(batch)
page += 1
return usersJavaScript (Node.js / browser)
Il fetch integrato restituisce una promise; il parsing del JSON si fa con il metodo .json().
const url = "https://api.example.com/users/42";
const response = await fetch(url, {
headers: { Authorization: "Bearer YOUR_TOKEN" },
});
// Controllo dello stato
if (!response.ok) {
throw new Error(`Errore API: ${response.status}`);
}
// Parsing del JSON
const user = await response.json();
console.log(user.name);Esempio di invio di dati (POST):
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_TOKEN",
},
body: JSON.stringify({ name: "Anna", role: "admin" }), // serializzazione
});
const created = await response.json();Java
Il Java moderno include un HttpClient integrato; per il parsing del JSON si usa una libreria (qui Jackson).
import java.net.URI;
import java.net.http.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
public class ApiExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users/42"))
.header("Authorization", "Bearer YOUR_TOKEN")
.GET()
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("Errore API: " + response.statusCode());
}
ObjectMapper mapper = new ObjectMapper();
JsonNode user = mapper.readTree(response.body());
System.out.println(user.get("name").asText());
}
}Go
La libreria standard fornisce sia il client HTTP (net/http) sia il parser (encoding/json).
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type User struct {
Name string `json:"name"`
}
func main() {
req, _ := http.NewRequest("GET", "https://api.example.com/users/42", nil)
req.Header.Set("Authorization", "Bearer YOUR_TOKEN")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
panic(fmt.Sprintf("Errore API: %d", resp.StatusCode))
}
var user User
json.NewDecoder(resp.Body).Decode(&user) // parsing del corpo in streaming
fmt.Println(user.Name)
}C
In .NET si usano HttpClient e il System.Text.Json integrato.
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
record User(string Name);
class Program
{
static async Task Main()
{
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_TOKEN");
var response = await client.GetAsync("https://api.example.com/users/42");
response.EnsureSuccessStatusCode();
// Parsing del JSON direttamente in un oggetto tipizzato
var user = await response.Content.ReadFromJsonAsync<User>();
Console.WriteLine(user?.Name);
}
}PHP
Con cURL si esegue la richiesta, mentre json_decode fa il parsing della risposta.
<?php
$ch = curl_init("https://api.example.com/users/42");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer YOUR_TOKEN"]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new Exception("Errore API: $status");
}
// Parsing del JSON in un array associativo
$user = json_decode($body, true);
echo $user["name"];Rust
L’accoppiata più diffusa: il client asincrono reqwest e serde per il parsing.
use serde::Deserialize;
#[derive(Deserialize)]
struct User {
name: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let response = client
.get("https://api.example.com/users/42")
.header("Authorization", "Bearer YOUR_TOKEN")
.send()
.await?;
if !response.status().is_success() {
return Err(format!("Errore API: {}", response.status()).into());
}
// Parsing del JSON in una struct
let user: User = response.json().await?;
println!("{}", user.name);
Ok(())
}Confronto degli strumenti
| Linguaggio | Client HTTP | Parsing del JSON |
|---|---|---|
| Python | requests / httpx |
.json() (json) |
| JavaScript | fetch / axios |
.json() (JSON) |
| Java | HttpClient |
Jackson / Gson |
| Go | net/http |
encoding/json |
| C# | HttpClient |
System.Text.Json |
| PHP | cURL / Guzzle | json_decode |
| Rust | reqwest |
serde / serde_json |
Buone pratiche
Per costruire un’integrazione affidabile con un’API conviene attenersi ad alcuni principi:
- Controlla sempre il codice di stato prima di fare il parsing del corpo della risposta.
- Racchiudi il parsing nella gestione degli errori — i dati esterni non sono affidabili.
- Non tenere i segreti nel codice — usa le variabili d’ambiente.
- Rispetta i limiti di richieste — applica pause e backoff in caso di 429.
- Imposta un timeout su ogni richiesta, per non rimanere bloccato.
- Logga richieste ed errori — semplifica il debug delle integrazioni.
- Metti in cache i dati che cambiano di rado, per ridurre il carico e non esaurire i limiti.
- Fissa la versione dell’API e segui gli annunci di deprecazione.
- Valida la struttura della risposta prima di usarne i campi.
In sintesi
L’estrazione di dati via API è il ciclo completo di interazione con un servizio esterno: costruzione della richiesta, autenticazione, verifica dello stato e degli header, parsing del corpo della risposta e gestione dei casi limite — paginazione, limiti, guasti di rete. Tecnicamente il passaggio di parsing dei dati si riduce quasi sempre al parsing di JSON, ma un’integrazione solida richiede di tenere conto anche di tutto ciò che gli sta intorno.
Gli strumenti esistono in ogni linguaggio diffuso: in alcuni il client HTTP e il parser sono integrati, in altri si ricorre a librerie popolari. Il principio è ovunque lo stesso — trasformare la risposta di un servizio remoto in dati affidabili con cui lavorare in sicurezza nel codice.
Per approfondire la fase di parsing dei dati vedi l’articolo «Parsing di JSON»; per il parsing degli altri formati di risposta, gli articoli «Parsing di XML» e «Parsing di CSV».