Utente:Wiccio/Tool:RipWikiquote
Allineamento date biografiche da Wikidata
[modifica]Questo strumento consiste in uno script Python progettato per allineare le date di nascita e di morte presenti nelle intestazioni delle voci di it.wikiquote.org con i dati contenuti in Wikidata.
Lo script è destinato alle voci relative a esseri umani e interviene esclusivamente sulla prima intestazione della pagina, senza modificare il resto del contenuto.
Obiettivo
[modifica]Lo scopo principale dello strumento è:
- migliorare la coerenza tra Wikiquote e Wikidata;
- ridurre discrepanze nelle informazioni biografiche;
- semplificare il lavoro di manutenzione delle voci;
- evitare aggiornamenti manuali ripetitivi.
Lo script adotta un approccio conservativo: modifica una voce solo quando i dati presenti risultano effettivamente diversi da quelli di Wikidata.
Ambito di intervento
[modifica]Lo script agisce solo sull’intestazione iniziale, tipicamente nella forma:
Nome Cognome (anno di nascita – anno di morte), descrizione…
Sono gestite anche intestazioni più complesse, ad esempio:
- pseudonimi o alter ego;
- nomi estesi o titoli;
- qualificazioni linguistiche o storiche.
Non vengono mai modificati:
- citazioni;
- testo discorsivo;
- immagini e didascalie;
- template;
- note e riferimenti;
- collegamenti wiki.
Fonte dei dati
[modifica]Le informazioni vengono lette da Wikidata, utilizzando le proprietà:
- P569 – data di nascita
- P570 – data di morte
La selezione dei dati segue queste priorità:
- affermazioni con rank “preferred”;
- in assenza di preferred, affermazione con il maggior numero di riferimenti;
- in ulteriore assenza, la prima affermazione disponibile.
Gestione delle date
[modifica]Lo script è in grado di interpretare e riportare correttamente:
- anni precisi;
- secoli (es. “VII secolo”, “VI secolo a.C.”);
- date approssimative (“circa”);
- decenni (es. “1100 circa”);
- date avanti Cristo;
- casi in cui è nota solo la data di morte;
- persone viventi (con la dicitura “vivente”).
Il formato finale è adattato allo stile comunemente utilizzato su it.wikiquote.org.
Criteri di modifica
[modifica]Una voce viene modificata solo se almeno una delle seguenti condizioni è vera:
- la data di nascita su Wikiquote è diversa da quella su Wikidata;
- la data di morte su Wikiquote è diversa da quella su Wikidata;
- su Wikiquote è presente una data che non risulta supportata da Wikidata.
Se le informazioni coincidono, la voce viene lasciata invariata.
Sicurezza e protezioni
[modifica]Per evitare modifiche indesiderate, lo script:
- esclude automaticamente contenuti tra [[ ]], {{ }}, <ref> e tag HTML;
- limita la modifica alla prima occorrenza dell’intestazione;
- interrompe l’elaborazione se il formato della voce non è riconosciuto.
Utilizzo e raccomandazioni
[modifica]Questo strumento è pensato come supporto alla manutenzione, non come sostituto del controllo umano.
Si raccomanda di:
- utilizzarlo su un numero limitato di voci per sessione;
- verificare manualmente i casi storici o ambigui;
- usare sempre un riassunto di modifica chiaro;
- rispettare le linee guida sull’uso di strumenti automatici.
Limitazioni note
[modifica]- Non gestisce voci non riconducibili a esseri umani.
- Non interviene su più intestazioni nella stessa pagina.
- Dipende dalla qualità e dalla precisione dei dati presenti in Wikidata.
Licenza e riutilizzo
[modifica]Il codice sorgente dello script è liberamente utilizzabile, condivisibile e modificabile.
Chiunque può:
- usarlo per scopi personali di manutenzione;
- adattarlo alle proprie esigenze;
- migliorarne o estenderne le funzionalità;
- redistribuirlo, anche in forma modificata.
L’unica raccomandazione è quella di rispettare le linee guida dei progetti Wikimedia e di utilizzare lo strumento in modo responsabile, specialmente in caso di modifiche automatiche su larga scala.
Codice sorgente
[modifica]# ===============================================================
# SCRIPT DI SINCRONIZZAZIONE DATE NASCITA/MORTE
# da Wikidata a it.wikiquote.org
#
# Obiettivo:
# - leggere date di nascita (P569) e morte (P570) da Wikidata
# - confrontarle con l'intestazione della voce su Wikiquote
# - aggiornare l'intestazione solo se necessario
#
# Principi adottati:
# - Wikidata ha priorità su Wikiquote
# - si preserva sempre la struttura testuale originale
# - nessuna modifica a wikilink, template, ref o HTML
# ===============================================================
import pywikibot
import re
from SPARQLWrapper import SPARQLWrapper, JSON
# QID iniziale
START_QID = "Q1"
# ---------------------------------------------------------------
# DEFINIZIONE DEI TRATTINI
#
# In Wikiquote potrebbero essere usati molti tipi di trattino
# (–, -, —, −, ecc.). Li normalizzo in un'unica regex
# per separare nascita e morte in modo canonico.
# ---------------------------------------------------------------
TRATTINI = r"–\-˗‒—―−ꟷ"
REGEX_TRATTINI = f"[{TRATTINI}]"
# ---------------------------------------------------------------
# PATTERN DELL'INTESTAZIONE
#
# Cattura:
# 1) tutto ciò che precede la parentesi con le date (incipit)
# 2) il contenuto della parentesi che contiene cifre o "secolo"
#
# Questo permette di gestire:
# - nomi semplici
# - pseudonimi / alter ego
# - titoli onorifici
# - testo descrittivo prima delle date
# ---------------------------------------------------------------
pattern = (
r"(.+?)"
r"\s*\("
r"([^()]*?(?:\d|secolo|\?)[^()]*)"
r"\)"
)
# ---------------------------------------------------------------
# ESTRAZIONE E FORMATTAZIONE DELLE DATE DA WIKIDATA
#
# La funzione converte il TimeValue Wikidata in una stringa
# compatibile con lo stile di it.wikiquote.org
#
# Precisioni gestite:
# - 7 → secolo (es. "VII secolo", "VII secolo a.C.")
# - 8 → decennio → reso come "anno circa" (es. "1100 circa")
# - ≥9 → anno preciso
#
# Eventuali qualificatori "circa" (P1480 = Q5727902)
# vengono sempre rispettati.
# ---------------------------------------------------------------
def estrai_anno(statement):
if not statement:
return None
data = statement.getTarget()
precision = getattr(data, "precision", None)
try:
year = data.year
except Exception:
return None
# --- SECOLO ---
if precision == 7:
if year < 0:
secolo = ((abs(year) - 1) // 100) + 1
risultato = f"{int_to_roman(secolo)} secolo a.C."
else:
secolo = ((year - 1) // 100) + 1
risultato = f"{int_to_roman(secolo)} secolo"
# --- DECENNIO ---
elif precision == 8:
risultato = f"{abs(year)} a.C. circa" if year < 0 else f"{year} circa"
# --- ANNO ---
elif precision >= 9:
risultato = f"{abs(year)} a.C." if year < 0 else str(year)
else:
return None
# --- QUALIFICATORE "CIRCA" ---
qualifiers = getattr(statement, "qualifiers", {})
if "P1480" in qualifiers:
for qual in qualifiers["P1480"]:
if getattr(qual.getTarget(), "id", None) == "Q5727902":
risultato += " circa"
break
return risultato
# ---------------------------------------------------------------
# CONVERSIONE NUMERI ROMANI
# Usata esclusivamente per i secoli
# ---------------------------------------------------------------
def int_to_roman(num):
val = [1000,900,500,400,100,90,50,40,10,9,5,4,1]
syms = ["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"]
roman = ""
i = 0
while num > 0:
for _ in range(num // val[i]):
roman += syms[i]
num -= val[i]
i += 1
return roman
# ---------------------------------------------------------------
# SCELTA DELLO STATEMENT PIÙ AFFIDABILE
#
# Ordine di priorità:
# 1) statement con rank "preferred"
# 2) statement con più riferimenti
# 3) primo statement disponibile
# ---------------------------------------------------------------
def prendi_statement_preferito(statements):
if not statements:
return None
preferiti = [s for s in statements if s.getRank() == "preferred"]
if preferiti:
return preferiti[0]
return sorted(
statements,
key=lambda s: len(getattr(s, "sources", [])),
reverse=True
)[0]
# ---------------------------------------------------------------
# PROTEZIONE DELLE SEZIONI NON MODIFICABILI
#
# Prima di operare sul testo copio e mantengo:
# - wikilink [[...]]
# - template {{...}}
# - ref <ref>...</ref>
# - tag HTML
#
# vengono sostituiti con segnaposto temporanei
# per evitare modifiche indesiderate.
# ---------------------------------------------------------------
def proteggi_blocchi(testo):
segnaposti = []
pattern_unico = re.compile(
r"(\[\[.*?\]\]|\{\{.*?\}\}|<ref\b[^>]*?>.*?</ref>|<[^>]+>)",
re.DOTALL
)
def salva(match):
segnaposti.append(match.group(0))
return f"@@PROT{len(segnaposti)-1}@@"
testo = pattern_unico.sub(salva, testo)
return testo, segnaposti
def ripristina_blocchi(testo, segnaposti):
return re.sub(
r"@@PROT(\d+)@@",
lambda m: segnaposti[int(m.group(1))],
testo
)
# ---------------------------------------------------------------
# FUNZIONE PRINCIPALE
#
# - interroga Wikidata
# - itera su tutte le persone (Q5) con sitelink su it.wikiquote
# - confronta e aggiorna l'intestazione se necessario
# ---------------------------------------------------------------
def main():
site_wd = pywikibot.Site("wikidata", "wikidata")
repo = site_wd.data_repository()
query = """
SELECT ?item ?title WHERE {
?item wdt:P31 wd:Q5 .
?sitelink schema:about ?item ;
schema:isPartOf <https://it.wikiquote.org/> ;
schema:name ?title .
}
"""
sparql = SPARQLWrapper("https://query.wikidata.org/sparql")
sparql.setQuery(query)
sparql.setReturnFormat(JSON)
results = sparql.query().convert()
print(f"Trovati {len(results['results']['bindings'])} elementi da processare.\n")
for row in results["results"]["bindings"]:
qid = row["item"]["value"].split("/")[-1]
title = row["title"]["value"]
print(f"\n--- PROCESSO {qid} : {title} ---")
item = pywikibot.ItemPage(repo, qid)
item.get()
site_wq = pywikibot.Site("it", "wikiquote")
page = pywikibot.Page(site_wq, title)
testo = page.text
# P31 = Q5
if "P31" not in item.claims:
print(" → Nessuna P31, salto.")
continue
if not any(c.getTarget().id == "Q5" for c in item.claims["P31"]):
print(" → Non è un essere umano (Q5), salto.")
continue
nas = prendi_statement_preferito(item.claims.get("P569", []))
mor = prendi_statement_preferito(item.claims.get("P570", []))
anno_nascita = estrai_anno(nas)
anno_morte = estrai_anno(mor)
if not anno_nascita and not anno_morte:
print(" → Nascita e morte non disponibili su Wikidata, salto.")
continue
# Proteggo sezioni non modificabili
testo_protetto, segnaposti = proteggi_blocchi(testo)
match = re.search(pattern, testo_protetto, re.DOTALL)
if not match:
print(" → Formato non riconosciuto, salto.")
continue
incipit = match.group(1).rstrip()
incipit = re.sub(r"\s*\($", "", incipit)
contenuto = match.group(2).strip()
# Estrazione nascita/morte
if re.search(REGEX_TRATTINI, contenuto):
parti = re.split(rf"\s*{REGEX_TRATTINI}\s*", contenuto)
nascita_wq = parti[0].strip()
morte_wq = parti[1].strip() if len(parti) > 1 else None
if morte_wq and morte_wq.lower() in ("vivente", "", " "):
morte_wq = None
# ---------------------------------------------------------
# NORMALIZZAZIONE DEI VALORI "IGNOTI" SU WIKIQUOTE
#
# Sequenze come "????" non rappresentano una data reale,
# ma l'assenza di informazione. Vanno quindi trattate
# come None per permettere a Wikidata di avere priorità.
# ---------------------------------------------------------
if nascita_wq and re.fullmatch(r"\?+", nascita_wq):
nascita_wq = None
if "?" in contenuto:
modifica = True
modifica = (
anno_nascita != nascita_wq
or (anno_morte and anno_morte != morte_wq)
or (not anno_morte and morte_wq is not None)
)
if not modifica:
print(" → Nessuna modifica necessaria.")
continue
# -------------------------------------------------------------
# LOGICA DEFINITIVA PER NASCITA/MORTE
# -------------------------------------------------------------
if anno_nascita and anno_morte:
# Caso normale: entrambe presenti su Wikidata
nuova_parentesi = f"({anno_nascita} – {anno_morte})"
elif anno_nascita and not anno_morte:
# Wikidata ha solo la nascita → vivente
nuova_parentesi = f"({anno_nascita} – vivente)"
elif not anno_nascita and anno_morte:
# Wikidata ha solo la morte → caso raro, ma gestito
nuova_parentesi = f"(... – {anno_morte})"
else:
# Caso impossibile per Q5, ma mettiamo una sicurezza
nuova_parentesi = ""
# Ricostruzione della stringa finale preservando grassetto
start, end = match.span()
testo_mod = (
testo_protetto[:start]
+ incipit
+ " "
+ nuova_parentesi
+ testo_protetto[end:]
)
# Ripristina sezioni protette
testo_finale = ripristina_blocchi(testo_mod, segnaposti)
page.text = testo_finale
try:
page.save(summary="Anno nascita/morte da Wikidata")
print(" ✓ Modificato correttamente!")
except Exception as e:
print(f" ✗ Errore nel salvataggio: {e}")
continue
if __name__ == "__main__":
main()