Fonte: OpenCoesione · ultimo aggiornamento dei dati: 30/06/2026EN
Progetto indipendente, a titolo didattico, con dati ufficiali.

← Tutorial

Costruisci una versione base di questo sito, passo passo

In un quarto d'ora crei una piccola pagina web che scarica da OpenCoesione i progetti di un Comune, somma finanziato e pagato e elenca i progetti in corso più grandi. È una versione di massima ma funzionante di questo sito, non una copia.

Ogni passo qui sotto è stato eseguito davvero partendo da una cartella vuota; l'esito è dichiarato in fondo.

Cosa ottieni

Una pagina, sito/index.html, con: totale finanziato e totale pagato di tutti i progetti del Comune (separati), i 15 progetti in corso più grandi, la fonte con la licenza e la data di scarico.

Cosa ti serve

Passo 1. Crea una cartella vuota

mkdir mini-sito
cd mini-sito

Passo 2. Salva il programma

Crea in quella cartella un file chiamato costruisci.py e incolla il codice qui sotto, oppure scaricalo e rinominalo. Leggilo prima: i commenti spiegano ogni parte.

"""Mini sito dei progetti pubblici di un Comune, dai dati OpenCoesione. Solo libreria standard di Python."""
import html, json, os, time, urllib.error, urllib.request
from datetime import date

COMUNE = "arezzo-comune"          # cambia questo valore per un altro Comune
URL = "https://opencoesione.gov.it/it/api/progetti/?format=json&territorio=" + COMUNE + "&page_size=500&page="
os.makedirs("cache", exist_ok=True)

def scarica(pagina):
    """Scarica una pagina di risultati; se l'hai già scaricata la rilegge dal disco."""
    nome = f"cache/pagina-{pagina}.json"
    if os.path.exists(nome):
        return json.load(open(nome, encoding="utf-8"))
    time.sleep(1)                  # pausa di cortesia: oltre circa una richiesta al secondo il servizio risponde 429
    richiesta = urllib.request.Request(URL + str(pagina), headers={"User-Agent": "mini-sito-didattico/1.0"})
    for tentativo in range(1, 6):
        try:
            with urllib.request.urlopen(richiesta, timeout=90) as risposta:
                dati = json.load(risposta)
            break
        except urllib.error.HTTPError as errore:
            if errore.code != 429 or tentativo == 5:
                raise
            time.sleep(20 * tentativo)     # troppe richieste: aspetta di più e riprova
    json.dump(dati, open(nome, "w", encoding="utf-8"), ensure_ascii=False)
    return dati

progetti, pagina = [], 1
while True:
    dati = scarica(pagina)
    progetti += dati["results"]
    if pagina >= dati["last_page"]:
        break
    pagina += 1
print(f"Scaricate {pagina} pagine, {len(progetti)} progetti")

euro = lambda testo: float((testo or "0").replace(",", "."))      # "51817680,65" -> 51817680.65
fmt = lambda x: f"{round(x):,}".replace(",", ".") + " €"
finanziato = sum(euro(p["oc_finanz_tot_pub_netto"]) for p in progetti)
pagato = sum(euro(p["tot_pagamenti"]) for p in progetti)
in_corso = sorted((p for p in progetti if p["oc_stato_progetto"] == "In corso"), key=lambda p: -euro(p["oc_finanz_tot_pub_netto"]))
print("Finanziato:", fmt(finanziato), "- Già pagato:", fmt(pagato), "- In corso:", len(in_corso))

righe = "".join(
    f"<tr><td>{html.escape(p['oc_titolo_progetto'])}</td><td>{fmt(euro(p['oc_finanz_tot_pub_netto']))}</td><td>{fmt(euro(p['tot_pagamenti']))}</td></tr>"
    for p in in_corso[:15])
pagina_html = f"""<!doctype html>
<html lang="it"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Progetti pubblici ad Arezzo</title>
<style>body{{font:18px/1.5 system-ui,sans-serif;max-width:900px;margin:0 auto;padding:16px;color:#1b2330}}
table{{border-collapse:collapse;width:100%}}td,th{{border-bottom:1px solid #d9e0ea;padding:6px;text-align:left}}.n{{font-size:1.6rem;font-weight:700}}</style></head><body>
<p>Fonte: <a href="https://opencoesione.gov.it/">OpenCoesione</a> (licenza CC BY 4.0). Dati scaricati il {date.today().strftime('%d/%m/%Y')}.</p>
<h1>Progetti pubblici ad Arezzo</h1>
<p><span class="n">{fmt(finanziato)}</span> finanziati e <span class="n">{fmt(pagato)}</span> già pagati, su {len(progetti)} progetti.</p>
<h2>I 15 progetti in corso più grandi</h2>
<div style="overflow-x:auto"><table><tr><th>Titolo</th><th>Finanziato</th><th>Già pagato</th></tr>{righe}</table></div>
</body></html>"""
os.makedirs("sito", exist_ok=True)
open("sito/index.html", "w", encoding="utf-8").write(pagina_html)
print("Creato sito/index.html")

Passo 3. Eseguilo

python3 costruisci.py

Il programma scarica 26 pagine da 500 progetti, aspettando un secondo tra una richiesta e l'altra, e conserva ogni pagina nella cartella cache: se si ferma, rilancialo e riparte da dove era arrivato. Richiede circa tre minuti. Ecco cosa abbiamo ottenuto:

Scaricate 26 pagine, 12590 progetti
Finanziato: 357.478.579 € - Già pagato: 291.577.787 € - In corso: 473
Creato sito/index.html

Passo 4. Guarda il risultato

python3 -m http.server 8000 --directory sito

Apri http://localhost:8000 nel browser. Per fermare il server premi Ctrl+C. Le due cifre grandi sono quelle della prima pagina riferite a tutti i 12.590 progetti di Arezzo (la prima pagina conta un perimetro più ristretto, spiegato nel Metodo e fonti).

Come funziona, in quattro parti

  1. Scarico con pausa e memoria: scarica() chiede una pagina per volta, aspetta un secondo, aspetta di più e riprova se il servizio risponde 429 («troppe richieste»), e salva ogni risposta in cache.
  2. Paginazione: il ciclo continua fino a last_page, come spiegato nel tutorial.
  3. Somme: gli importi sono testo con la virgola, quindi euro() trasforma "51817680,65" in un numero. Finanziato e pagato sono sommati separatamente.
  4. La pagina: un modello di testo con le cifre e una tabella; html.escape protegge la pagina dai caratteri speciali nei titoli.

E poi

Esito della nostra prova

Eseguito il 2 novembre 2026 da una cartella vuota, con Python 3.12.3 su Linux, seguendo esattamente i passi qui sopra. Il primo tentativo è fallito: il servizio ha risposto 429 a metà scarico, perché la pausa di un secondo non bastava. Abbiamo aggiunto l'attesa con nuovi tentativi che vedi nel codice, ripetuto la prova da una cartella vuota ed è arrivata in fondo in 3 minuti e 32 secondi. I totali coincidono con quelli della lista completa dei progetti di Arezzo usata da questo sito. Dopo la prova abbiamo avvolto la tabella in un contenitore scorrevole perché sul telefono (390 px) usciva dallo schermo; l'abbiamo ricontrollato rilanciando il programma con la cache già scaricata. Non provato: Windows e macOS, altre versioni di Python, altri Comuni.