Terminal / tools /content_cleaner.py
Pulka
deploy: backend build-1781037398298 — 2026-06-09 20:36 UTC
7cc12d8 verified
Raw
History Blame Contribute Delete
5.14 kB
"""
content_cleaner.py — Pulisce e struttura il testo per il modello.
W-NAV: aggiunto extract_with_trafilatura() come primary extractor.
Strategia:
1. trafilatura.extract() — algoritmo Readability-quality, puro Python
2. Fallback: strip HTML tags + clean_and_structure() esistente (regex-based)
se trafilatura non installato o restituisce < 200 chars
Problematiche gestite:
- trafilatura su pagine non-article (login, 404, SPA vuota) → None → fallback
- ImportError: trafilatura non installato → fallback silenzioso
- Fallback con HTML grezzo: strip tag prima di passare a clean_and_structure
(evita che HTML residuo finisca nel testo inviato all'LLM)
- favor_recall=True: preferisce recall a precision (meglio più testo che meno)
- include_tables=True: tabelle importanti per dati tecnici/finanziari
- deduplicate=True: rimuove paragrafi boilerplate duplicati (footer, nav)
"""
import re
from typing import Optional
NOISE_PATTERNS = [
"cookie", "accept all cookies", "privacy policy", "terms of service",
"all rights reserved", "subscribe", "follow us on", "share this article",
]
def remove_noise(text: str) -> str:
lines = text.split("\n")
cleaned = []
for line in lines:
ll = line.lower().strip()
if len(ll) < 4:
continue
if any(p in ll for p in NOISE_PATTERNS):
continue
if line.count("|") > 5:
continue
cleaned.append(line)
return "\n".join(cleaned)
def extract_key_paragraphs(text: str, query: str, max_paragraphs: int = 6) -> list[str]:
q_words = set(w.lower() for w in re.split(r"\W+", query) if len(w) > 3)
pars = [p.strip() for p in re.split(r"\n{2,}", text) if len(p.strip()) > 60]
def rel(p: str) -> float:
pl = p.lower()
return sum(1 for w in q_words if w in pl) / max(1, len(q_words))
return sorted(pars, key=rel, reverse=True)[:max_paragraphs]
def _strip_html(html: str) -> str:
"""Strip HTML tags — usato nel fallback di extract_with_trafilatura."""
text = re.sub(r"<(script|style|nav|footer|header|noscript|aside)[^>]*>.*?</\1>",
" ", html, flags=re.S | re.I)
text = re.sub(r"<[^>]+>", " ", text)
text = re.sub(r"[ \t]+", " ", text)
text = re.sub(r"\n{3,}", "\n\n", text)
return text.strip()
def clean_and_structure(text: str, query: Optional[str] = None, max_chars: int = 4000) -> dict:
text = remove_noise(text)
pars = extract_key_paragraphs(text, query) if query else [text]
structured = "\n\n".join(pars)
if len(structured) > max_chars:
structured = structured[:max_chars] + "\n[troncato]"
return {
"content": structured,
"chars": len(structured),
"words": len(structured.split()),
"query_used": query,
"extractor": "regex",
}
def extract_with_trafilatura(
html: str,
url: str = "",
query: Optional[str] = None,
max_chars: int = 5000,
) -> dict:
"""
Primary extractor: trafilatura (Readability-quality, puro Python).
Fallback a clean_and_structure() con strip HTML preventivo se trafilatura
non disponibile o restituisce contenuto insufficiente.
Parametri:
html — HTML grezzo della pagina (non testo pre-pulito)
url — URL originale (aiuta trafilatura a contestualizzare il dominio)
query — query opzionale per filtrare paragrafi rilevanti
max_chars — limite caratteri output
"""
try:
import trafilatura # type: ignore[import-untyped]
extracted = trafilatura.extract(
html,
url=url or None,
include_comments=False,
include_tables=True,
include_images=False,
deduplicate=True,
favor_recall=True,
)
if extracted and len(extracted.strip()) > 200:
# Filtra per rilevanza solo su testi lunghi (>1000 chars)
# Su testi brevi il ranking riduce troppo il contenuto utile
if query and len(extracted) > 1000:
pars = extract_key_paragraphs(extracted, query, max_paragraphs=8)
structured = "\n\n".join(pars)
else:
structured = extracted
if len(structured) > max_chars:
structured = structured[:max_chars] + "\n[troncato]"
return {
"content": structured,
"chars": len(structured),
"words": len(structured.split()),
"query_used": query,
"extractor": "trafilatura",
}
except ImportError:
pass # trafilatura non installato
except Exception:
pass # HTML malformato o altro errore inatteso
# Fallback: strip HTML poi regex-based cleaner
# CRITICO: strip tag prima di passare a clean_and_structure —
# altrimenti i tag HTML finiscono nel testo inviato all'LLM
stripped = _strip_html(html)
result = clean_and_structure(text=stripped, query=query, max_chars=max_chars)
result["extractor"] = "regex"
return result