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
- Python 3 (versione 3.8 o successiva; noi abbiamo usato la 3.12). Controlla con
python3 --version(su Windows:python --version). Se manca, installalo da python.org. - Un terminale (Terminale su Mac e Linux, PowerShell su Windows) e un browser.
- Una connessione a internet. Non devi installare altro: il programma usa solo la libreria standard di Python.
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
- 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 incache. - Paginazione: il ciclo continua fino a
last_page, come spiegato nel tutorial. - Somme: gli importi sono testo con la virgola, quindi
euro()trasforma"51817680,65"in un numero. Finanziato e pagato sono sommati separatamente. - La pagina: un modello di testo con le cifre e una tabella;
html.escapeprotegge la pagina dai caratteri speciali nei titoli.
E poi
- Cambia
COMUNEcon un altro Comune: i codici sono nella sezione territori della API. - Aggiungi una colonna con lo stato o un link alla scheda ufficiale di ogni progetto (campo
oc_link). - Pubblica: la cartella
sitoè un normale sito statico; copiala su un qualsiasi servizio di hosting statico. - Guarda come è fatto questo sito completo: il codice è nel repository del progetto e le pagine sono generate da uno script.
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.