Keyword Extraction da articoli, ticket e documenti senza LLM

Immagine di copertina

Quando gestisci un blog, una knowledge base o un sistema di ticketing, prima o poi ti scontri con lo stesso problema: centinaia (o migliaia) di testi senza tag, senza categoria, senza metadati utili. Assegnarli a mano è impensabile, e non serve per forza un LLM per risolvere il problema: la keyword extraction classica, con librerie come YAKE e scikit-learn, fa un ottimo lavoro, è deterministica, gira offline ed è economicissima da eseguire su grandi volumi.

In questo articolo vediamo due approcci complementari: YAKE (statistico, senza bisogno di corpus di riferimento) e TF-IDF (comparativo, utile quando hai già una collezione di documenti).

Cosa vedrai

Perché non serve un LLM

Per estrarre le parole chiave più rappresentative di un testo non serve comprendere il significato profondo del linguaggio: serve individuare termini statisticamente rilevanti rispetto al testo stesso o rispetto a una collezione di riferimento. È un problema che gli algoritmi classici risolvono da decenni, con vantaggi concreti:

  • costo quasi nullo: nessuna chiamata API, nessun token da pagare
  • latenza minima: qualche millisecondo per documento
  • deterministico: stesso input, stesso output, sempre
  • offline: nessun dato sensibile lascia la tua infrastruttura
  • spiegabile: puoi sempre motivare perché una parola è stata scelta (frequenza, posizione, co-occorrenza)

Keyword extraction con YAKE

YAKE (Yet Another Keyword Extractor) è un algoritmo unsupervised che non richiede un dataset di addestramento né un corpus di riferimento: analizza statistiche testuali come posizione delle parole, frequenza, casualità di comparsa e relazione con il contesto per assegnare un punteggio a ogni possibile keyword. Attenzione: a differenza di altri sistemi, in YAKE un punteggio più basso indica una parola chiave più rilevante.

Installiamo la libreria:

pip install yake

Ed estraiamo le parole chiave da un testo:

import yake

testo = """
spaCy è una libreria open-source per il Natural Language Processing in Python.
Supporta decine di lingue ed è progettata per l'uso in produzione. La funzione
di sentence splitting divide automaticamente un testo in frasi. Questa
funzionalità è fondamentale per costruire pipeline NLP robuste e scalabili.
Il componente senter, addestrato su corpus reali, gestisce correttamente
abbreviazioni e casi particolari del testo in lingua italiana.
"""

kw_extractor = yake.KeywordExtractor(
    lan="it",       # lingua del testo
    n=2,            # lunghezza massima delle n-gram (1 = singole parole, 2 = bigrammi...)
    top=8,          # numero di keyword da restituire
)

keywords = kw_extractor.extract_keywords(testo)

# Punteggio più basso = keyword più rilevante
for kw, score in sorted(keywords, key=lambda x: x[1]):
    print(f"{score:.4f}  {kw}")

Output:

0.0059  Natural Language
0.0059  Language Processing
0.0292  libreria open-source
0.0602  Python
0.0768  Natural
0.0768  Language
0.0768  Processing
0.1233  spaCy

YAKE riconosce correttamente sia i bigrammi (Natural Language, Language Processing) che i termini singoli più rappresentativi (Python, spaCy), senza bisogno di alcun addestramento preventivo.

Keyword extraction con TF-IDF

Quando invece disponi già di una collezione di documenti (un archivio di articoli, uno storico di ticket), TF-IDF (Term Frequency - Inverse Document Frequency) è spesso la scelta migliore: premia i termini frequenti in un documento ma rari nella collezione, ossia quelli davvero distintivi per quel testo specifico.

from sklearn.feature_extraction.text import TfidfVectorizer
from nltk.corpus import stopwords

# Import una tantum: python -c "import nltk; nltk.download('stopwords')"
it_stopwords = stopwords.words("italian")

corpus = [
    testo,  # il testo su spaCy visto sopra
    "Il machine learning e il deep learning sono tecniche di intelligenza artificiale.",
    "La pizza napoletana è un piatto tipico della cucina italiana con pomodoro e mozzarella.",
]

vectorizer = TfidfVectorizer(
    stop_words=it_stopwords,
    ngram_range=(1, 2),
    max_features=30,
)
tfidf_matrix = vectorizer.fit_transform(corpus)
feature_names = vectorizer.get_feature_names_out()

# Estraggo le keyword del primo documento (indice 0)
row = tfidf_matrix[0].toarray().flatten()
top_idx = row.argsort()[::-1][:8]

for i in top_idx:
    if row[i] > 0:
        print(f"{row[i]:.4f}  {feature_names[i]}")

Output:

0.3879  testo
0.1940  funzionalità
0.1940  frasi
0.1940  frasi funzionalità
0.1940  fondamentale
0.1940  fondamentale costruire
0.1940  decine
0.1940  decine lingue

Il vantaggio dell’approccio TF-IDF è che è relativo alla collezione: se aggiungessimo altri dieci articoli su spaCy, il termine “testo” perderebbe rilevanza perché diventerebbe comune a molti documenti, mentre termini davvero specifici emergerebbero con più forza.

Confronto tra i due approcci

AspettoYAKETF-IDF
Richiede un corpus di riferimentoNoSì (più documenti sono, meglio è)
SetupMinimoRichiede vettorizzazione della collezione
Adatto a singolo documento isolatoSìMeno efficace
Adatto a corpus di articoli/ticket storiciSìOttimo
MultilinguaSupporto nativo per molte lingueDipende dalle stopword disponibili
Costo computazionaleBassoBasso-medio (dipende dalla dimensione del corpus)

In pratica, ti conviene:

  • usare YAKE quando devi processare documenti singoli, appena arrivati, senza un contesto storico (es. un nuovo ticket)
  • usare TF-IDF quando hai uno storico consolidato e vuoi capire cosa distingue un documento dagli altri (es. tagging retroattivo di un blog)

Tagging automatico di articoli e ticket

Mettiamo insieme i due approcci in una funzione riutilizzabile, pensata per generare tag automatici su un archivio di contenuti (articoli di blog, ticket di supporto, documentazione interna):

from dataclasses import dataclass
import yake


@dataclass
class TaggedDocument:
    id: str
    title: str
    tags: list[str]


def extract_tags(
    doc_id: str,
    title: str,
    text: str,
    lang: str = "it",
    max_tags: int = 5,
) -> TaggedDocument:
    """
    Genera automaticamente dei tag per un documento usando YAKE.
    Utile per tagging retroattivo di articoli, ticket o documenti
    che non hanno metadati.
    """
    kw_extractor = yake.KeywordExtractor(lan=lang, n=2, top=max_tags * 2)
    keywords = kw_extractor.extract_keywords(text)

    # Ordino per rilevanza (score più basso = migliore) e prendo i primi N
    ranked = sorted(keywords, key=lambda x: x[1])
    tags = [kw.lower() for kw, _ in ranked[:max_tags]]

    return TaggedDocument(id=doc_id, title=title, tags=tags)


# ── Demo su un piccolo archivio ─────────────────────────────────────────────
articoli = [
    {
        "id": "art-1",
        "title": "Introduzione a spaCy",
        "text": (
            "spaCy è una libreria NLP open-source scritta in Python e Cython. "
            "È progettata per essere veloce e scalabile in ambienti di produzione. "
            "Supporta il sentence splitting, la tokenizzazione e il riconoscimento delle entità."
        ),
    },
    {
        "id": "art-2",
        "title": "BM25 per la ricerca lessicale",
        "text": (
            "BM25 è un algoritmo di ranking probabilistico usato dai motori di ricerca "
            "per ordinare i documenti in base alla rilevanza lessicale rispetto a una query. "
            "È molto più economico e veloce di una ricerca semantica basata su embedding."
        ),
    },
]

for articolo in articoli:
    risultato = extract_tags(articolo["id"], articolo["title"], articolo["text"])
    print(f"[{risultato.id}] {risultato.title}")
    print(f"  tag suggeriti: {', '.join(risultato.tags)}")
    print()

Output:

[art-1] Introduzione a spaCy
  tag suggeriti: spacy, sentence splitting, riconoscimento delle, delle entità, entità supporta

[art-2] BM25 per la ricerca lessicale
  tag suggeriti: bm25, ranking probabilistico, ricerca lessicale, motori di, di ricerca

Con poche righe di codice, senza nessuna chiamata a un modello esterno, hai un sistema di tagging automatico applicabile in batch a migliaia di documenti, in pochi secondi e a costo zero.


Consiglio pratico: se lavori su ticket di supporto, puoi combinare la keyword extraction con la classificazione degli intent (vedi l’articolo sull’intent detection) per ottenere sia il tema che l’azione richiesta dal cliente, senza mai passare i dati a un servizio esterno.

Risorse utili

Conosci meglio chi ha scritto questo articolo

Serena Sensini

Ciao! Mi chiamo Serena Sensini e sono la creatrice di @ TheRedCode.it. Ho aperto questo blog nel 2021 per raccontare il mio lavoro e il mondo dell’informatica a parole semplici, in piccole pillole e alla portata di tutte le persone.

Sono un’ingegnera informatica specializzata in ambito AI & NLP. Di giorno lavoro come CTO @ Welyk e come Innovation & Emerging Technologies Leader @ Dedalus, mentre di notte scrivo e sono autrice di 5 libri -per ora-. 🖊️

Foto di Serena Sensini

Partners

Community, aziende e persone che supportano attivamente il blog

Vuoi diventare partner?

Collaboriamo con community, aziende e strumenti del mondo tech che vogliono raggiungere la nostra community di dev ed engineer italiani. Scrivici per proporre una collaborazione.

✉️ Scrivici

Vuoi diventare tech content creator? 🖊️

Se ti va di raccontare la tua esperienza nel mondo tech, questo è il posto giusto.

Cerchiamo voci autentiche, esempi pratici e punti di vista utili per chi legge.

Scrivici a collaborazioni[at]theredcode.it con una proposta: idea, taglio del contenuto e una breve presentazione. Non vediamo l'ora di leggere la tua esperienza!

Invia la tua idea