Spaces:
Running
Running
File size: 5,138 Bytes
28a08e7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | """
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
|