W3docs

Python: Lavorare con le API (requests)

Impara a usare la libreria requests di Python per effettuare chiamate GET e POST, inviare header, parametri di query, gestire risposte JSON e gli errori.

La libreria requests è il modo standard per effettuare chiamate HTTP da Python. Avvolge il modulo di basso livello urllib in un'API pulita, così recuperare una pagina web o chiamare una REST API richiede una riga di codice invece di dieci. Questo capitolo copre tutto ciò di cui hai bisogno: installare requests, effettuare chiamate GET e POST, inviare header e parametri di query, lavorare con le risposte JSON, caricare file, usare le sessioni e gestire gli errori in modo robusto.

Installazione

requests non fa parte della libreria standard, quindi la installi con pip:

pip install requests

Se stai lavorando all'interno di un ambiente virtuale (consigliato), attivalo prima in modo che il pacchetto sia circoscritto al tuo progetto. Dopo l'installazione, verifica che funzioni:

import requests
print(requests.__version__)   # e.g. 2.32.3

Effettuare una richiesta GET

requests.get() invia una richiesta HTTP GET e restituisce un oggetto Response. Questa è l'operazione più comune — utilizzata per recuperare dati da API, pagine web e file.

import requests

response = requests.get("https://jsonplaceholder.typicode.com/todos/1")

print(response.status_code)   # 200
print(response.url)           # https://jsonplaceholder.typicode.com/todos/1
print(response.text)          # raw response body as a string

Output atteso:

200
https://jsonplaceholder.typicode.com/todos/1
{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}

Leggere la risposta come JSON

La maggior parte delle API moderne restituisce JSON. Chiama .json() sulla risposta invece di analizzare .text manualmente — internamente chiama json.loads() e restituisce un dict o una lista Python.

import requests

response = requests.get("https://jsonplaceholder.typicode.com/todos/1")
data = response.json()

print(data["title"])      # delectus aut autem
print(data["completed"])  # False

Consulta il capitolo Python JSON per i dettagli su come gli oggetti Python vengono mappati ai tipi JSON.

Inviare parametri di query

I parametri di query sono le coppie chiave-valore dopo il ? in un URL, come ?q=python&page=2. Passali come dict all'argomento paramsrequests li codifica nell'URL e li aggiunge automaticamente.

import requests

params = {
    "q": "python requests",
    "page": 1,
    "per_page": 5,
}

response = requests.get("https://httpbin.org/get", params=params)

# requests builds the full URL for you
print(response.url)
# https://httpbin.org/get?q=python+requests&page=1&per_page=5

Usa sempre params= invece di costruire l'URL a mano — gestisce correttamente i caratteri speciali e la codifica.

Inviare header di richiesta

Gli header trasportano metadati: token di autenticazione, preferenze di tipo di contenuto, chiavi API e altro ancora. Passali come dict a headers=:

import requests

headers = {
    "Accept": "application/json",
    "Authorization": "Bearer my-api-token",
    "User-Agent": "MyApp/1.0",
}

response = requests.get("https://httpbin.org/headers", headers=headers)
print(response.json())

Header comuni che invierai:

HeaderScopo
AuthorizationToken di autenticazione (Bearer, Basic, ecc.)
Content-TypeFormato del corpo della richiesta (es. application/json)
AcceptFormato che vuoi ricevere dal server
User-AgentIdentifica il tuo client presso il server
X-API-KeyChiave API in un header personalizzato (varia in base al servizio)

Effettuare una richiesta POST

requests.post() invia dati al server — usato per creare risorse, inviare moduli o chiamare azioni.

Inviare JSON

Passa un dict Python a json=. La libreria lo serializza e imposta automaticamente l'header Content-Type: application/json:

import requests

payload = {
    "title": "Buy groceries",
    "completed": False,
    "userId": 1,
}

response = requests.post(
    "https://jsonplaceholder.typicode.com/todos",
    json=payload,
)

print(response.status_code)   # 201 Created
print(response.json())

Output atteso:

201
{'title': 'Buy groceries', 'completed': False, 'userId': 1, 'id': 201}

Inviare dati di un modulo

Alcune API o moduli HTML si aspettano dati application/x-www-form-urlencoded. Usa data= invece di json=:

import requests

form_data = {
    "username": "alice",
    "password": "secret",
}

response = requests.post("https://httpbin.org/post", data=form_data)
print(response.status_code)

Inviare file (upload multipart)

Per caricare un file, aprilo in modalità binaria e passalo tramite files=:

import requests

with open("report.pdf", "rb") as f:
    response = requests.post(
        "https://httpbin.org/post",
        files={"file": f},
    )

print(response.status_code)

requests codifica l'upload come multipart/form-data, che è ciò che la maggior parte degli endpoint di caricamento file si aspetta.

Altri metodi HTTP

Le REST API usano diversi verbi HTTP per operazioni diverse. requests fornisce una funzione per ogni metodo:

import requests

base = "https://jsonplaceholder.typicode.com/todos/1"

# Update a resource (replace entirely)
response = requests.put(base, json={"title": "Updated", "completed": True, "userId": 1})
print(response.status_code)   # 200

# Partial update
response = requests.patch(base, json={"completed": True})
print(response.status_code)   # 200

# Delete a resource
response = requests.delete(base)
print(response.status_code)   # 200

Gestione degli errori

Controllare i codici di stato

Il codice di stato HTTP indica se la richiesta è riuscita. I gruppi più importanti:

IntervalloSignificato
2xxSuccesso (200 OK, 201 Created, 204 No Content)
3xxReindirizzamento (gestito automaticamente da requests)
4xxErrore del client (400 Bad Request, 401 Unauthorized, 404 Not Found)
5xxErrore del server (500 Internal Server Error, 503 Service Unavailable)

raise_for_status()

Chiamare .raise_for_status() su una risposta solleva automaticamente un'eccezione HTTPError se il codice di stato è 4xx o 5xx. È il modo più pulito per fallire rapidamente in caso di risposte errate:

import requests

response = requests.get("https://jsonplaceholder.typicode.com/todos/99999")

try:
    response.raise_for_status()
    data = response.json()
    print(data)
except requests.exceptions.HTTPError as err:
    print(f"HTTP error: {err}")

Senza raise_for_status(), una risposta 404 sembra un successo — il tuo codice legge un corpo di errore e lo elabora silenziosamente.

Gestire gli errori di rete

I fallimenti a livello di rete (errore di risoluzione DNS, connessione rifiutata, timeout) sollevano requests.exceptions.ConnectionError o requests.exceptions.Timeout. Catturali entrambi con la classe base requests.exceptions.RequestException:

import requests

try:
    response = requests.get("https://api.example.com/data", timeout=5)
    response.raise_for_status()
    data = response.json()
except requests.exceptions.Timeout:
    print("The request timed out — server took too long to respond.")
except requests.exceptions.ConnectionError:
    print("Could not connect — check your network or the URL.")
except requests.exceptions.HTTPError as err:
    print(f"HTTP error {response.status_code}: {err}")
except requests.exceptions.RequestException as err:
    print(f"Unexpected error: {err}")

Questo schema copre l'intera gerarchia delle eccezioni: timeout, connessione, errore HTTP e la classe base catch-all. Consulta Python try/except per un approfondimento sulla gestione delle eccezioni in Python.

Imposta sempre un timeout

Per impostazione predefinita requests attenderà all'infinito se il server non risponde mai. Passa sempre timeout= per evitare programmi che si bloccano:

# timeout=(connect_timeout, read_timeout) in seconds
response = requests.get("https://api.example.com/data", timeout=(3, 10))

La forma tupla imposta separatamente il timeout di connessione e il timeout di lettura. Un timeout di lettura di 10 secondi significa "attendi fino a 10 secondi tra un byte e l'altro dopo che la connessione è stata stabilita."

Usare le sessioni

Un oggetto requests.Session mantiene le impostazioni — header, cookie, autenticazione — attraverso più richieste allo stesso host. Riutilizza anche la connessione TCP sottostante (connection pooling), che è più veloce rispetto alla creazione di una nuova connessione per ogni chiamata.

import requests

with requests.Session() as session:
    # Set headers once — every request in this session will include them
    session.headers.update({
        "Authorization": "Bearer my-api-token",
        "Accept": "application/json",
    })

    # All requests reuse the connection and headers
    r1 = session.get("https://api.example.com/users")
    r2 = session.get("https://api.example.com/posts")
    r3 = session.post("https://api.example.com/todos", json={"title": "New"})

    print(r1.status_code, r2.status_code, r3.status_code)

Usa una sessione ogni volta che effettui più di una richiesta allo stesso server.

Ispezionare la risposta

L'oggetto Response espone tutto ciò che il server ha restituito:

import requests

response = requests.get("https://httpbin.org/get")

print(response.status_code)      # 200
print(response.reason)           # OK
print(response.headers)          # dict of response headers
print(response.headers["Content-Type"])  # application/json
print(response.encoding)         # utf-8
print(response.elapsed)          # how long the request took
print(response.url)              # final URL (after redirects)

Per contenuto binario (immagini, PDF), usa response.content (restituisce bytes) invece di response.text:

import requests

response = requests.get("https://httpbin.org/image/png")
with open("image.png", "wb") as f:
    f.write(response.content)

Autenticazione

HTTP Basic Auth

Passa una tupla (username, password) a auth=:

import requests

response = requests.get(
    "https://httpbin.org/basic-auth/alice/secret",
    auth=("alice", "secret"),
)
print(response.status_code)   # 200

Bearer token (chiavi API)

La maggior parte delle API moderne usa un bearer token nell'header Authorization:

import requests

headers = {"Authorization": "Bearer eyJhbGciOiJIUzI1NiIs..."}
response = requests.get("https://api.example.com/me", headers=headers)

Non inserire mai token hardcoded nei file sorgente. Caricali da variabili d'ambiente o da un gestore di segreti:

import os
import requests

token = os.environ["API_TOKEN"]
headers = {"Authorization": f"Bearer {token}"}
response = requests.get("https://api.example.com/me", headers=headers)

Esempio pratico — API di GitHub

Questo esempio mostra un pattern completo: sessione, bearer auth, paginazione, gestione degli errori e analisi JSON:

import os
import requests

GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "")
BASE_URL = "https://api.github.com"

with requests.Session() as session:
    session.headers.update({
        "Authorization": f"Bearer {GITHUB_TOKEN}",
        "Accept": "application/vnd.github+json",
        "X-GitHub-Api-Version": "2022-11-28",
    })

    try:
        # Fetch the first page of public repos for a user
        response = session.get(
            f"{BASE_URL}/users/torvalds/repos",
            params={"per_page": 5, "sort": "updated"},
            timeout=10,
        )
        response.raise_for_status()
        repos = response.json()

        for repo in repos:
            print(f"{repo['name']:40s}{repo['stargazers_count']}")

    except requests.exceptions.HTTPError as err:
        print(f"GitHub API error: {err}")
    except requests.exceptions.RequestException as err:
        print(f"Network error: {err}")

Questo pattern — sessione con header condivisi, raise_for_status(), try/except circoscritto — è l'approccio pronto per la produzione per qualsiasi client API che scrivi.

Richieste concorrenti con asyncio

Per i programmi che chiamano molti endpoint contemporaneamente, la libreria sincrona requests si blocca su ogni chiamata. Passa ad aiohttp (l'equivalente asincrono) e combinalo con il modulo asyncio di Python:

import asyncio
import aiohttp

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.json()

async def main():
    urls = [
        "https://jsonplaceholder.typicode.com/todos/1",
        "https://jsonplaceholder.typicode.com/todos/2",
        "https://jsonplaceholder.typicode.com/todos/3",
    ]
    async with aiohttp.ClientSession() as session:
        results = await asyncio.gather(*[fetch(session, u) for u in urls])
    for r in results:
        print(r["title"])

asyncio.run(main())

Consulta il capitolo Python asyncio per capire come funziona async/await prima di adottare questo pattern.

Riferimento rapido

OperazioneCodice
Richiesta GETrequests.get(url)
GET con parametrirequests.get(url, params={"key": "val"})
GET con headerrequests.get(url, headers={"Authorization": "Bearer token"})
POST corpo JSONrequests.post(url, json={"key": "val"})
POST dati modulorequests.post(url, data={"key": "val"})
Caricamento filerequests.post(url, files={"file": open("f.pdf", "rb")})
PUT / PATCH / DELETErequests.put/patch/delete(url, json=...)
Controllo statoresponse.status_code
Eccezione su 4xx/5xxresponse.raise_for_status()
Analisi corpo JSONresponse.json()
Corpo testo grezzoresponse.text
Corpo bytes grezzoresponse.content
Impostare timeoutrequests.get(url, timeout=5)
Riutilizzare connessionewith requests.Session() as s: ...

Capitoli correlati

Esercitazione

Pratica
Which argument sends a Python dict as a JSON body in a POST request?
Which argument sends a Python dict as a JSON body in a POST request?
Was this page helpful?