Spaces:
Configuration error
Configuration error
| """ | |
| 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 | |