fabgel's picture
Update app.py
7445308 verified
import gradio as gr
import requests
from bs4 import BeautifulSoup
import pandas as pd
from huggingface_hub import InferenceClient
import os
import json # Importiamo la libreria per gestire il formato JSON
# Legge il token dai secrets dello Space
HF_TOKEN = os.environ.get("HF_TOKEN")
# Inizializza il client per l'API di Hugging Face
client = InferenceClient(
model="mistralai/Mixtral-8x7B-Instruct-v0.1",
token=HF_TOKEN,
)
def clean_text(html_content):
"""Estrae e pulisce il testo dall'HTML."""
soup = BeautifulSoup(html_content, "html.parser")
for element in soup(["script", "style", "header", "footer", "nav", "aside"]):
element.decompose()
text = soup.get_text(separator=' ', strip=True)
return ' '.join(text.split())
def scrape_urls(urls):
"""Scansiona una lista di URL e restituisce il testo concatenato."""
all_text = ""
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}
for url in urls:
try:
response = requests.get(url.strip(), headers=headers, timeout=10)
response.raise_for_status()
all_text += clean_text(response.text) + " "
except requests.RequestException as e:
print(f"Errore durante lo scraping di {url}: {e}")
return all_text
def classify_keywords(urls_text, keywords_text, progress=gr.Progress()):
"""Funzione principale che orchestra lo scraping e la classificazione."""
if not HF_TOKEN:
raise gr.Error("Token Hugging Face non trovato! Assicurati di averlo inserito nei Secrets dello Space.")
urls = [url.strip() for url in urls_text.split('\n') if url.strip()]
keywords = list(set([kw.strip() for kw in keywords_text.split('\n') if kw.strip()]))
if not urls or not keywords:
raise gr.Error("Per favore, inserisci almeno un URL e una lista di keyword.")
# 1. Scraping
progress(0.1, desc="Fase 1/3: Sto analizzando il contenuto degli URL...")
website_content = scrape_urls(urls)
if not website_content:
raise gr.Error("Impossibile estrarre contenuto dagli URL forniti. Controlla che siano validi e accessibili.")
website_content = website_content[:15000]
# 2. Riassunto del contesto
progress(0.3, desc="Fase 2/3: Sto capendo l'argomento principale del sito...")
messages_summary = [
{"role": "user", "content": f"""Analizza il seguente testo estratto da un sito web e riassumi in 2-3 frasi i principali prodotti, servizi e le categorie tematiche trattate. Sii conciso e focalizzati su ciò che il sito vende o promuove. Testo del sito: "{website_content}" """}
]
try:
response_summary = client.chat_completion(messages_summary, max_tokens=200)
website_summary = response_summary.choices[0].message.content.strip()
except Exception as e:
raise gr.Error(f"Errore nella chiamata al modello per il riassunto: {e}")
# 3. Classificazione di TUTTE le keyword in un'unica chiamata (BATCH)
progress(0.6, desc="Fase 3/3: Sto classificando tutte le keyword in un colpo solo...")
# Formatta la lista di keyword per il prompt
keyword_list_str = "\n- ".join(keywords)
messages_classify = [
{"role": "user", "content": f"""Contesto del sito: "{website_summary}"
Data la lista di keyword qui sotto, classifica ciascuna di esse come "Pertinente" o "Non pertinente" rispetto al contesto del sito.
IMPORTANTE: Rispondi ESCLUSIVAMENTE con un array JSON valido. Ogni elemento dell'array deve essere un oggetto con due chiavi: "keyword" e "classification".
Esempio di output corretto:
[
{{"keyword": "piastrelle bagno", "classification": "Pertinente"}},
{{"keyword": "ricetta della pizza", "classification": "Non pertinente"}}
]
Ecco la lista di keyword da classificare:
- {keyword_list_str}
"""}
]
results = []
try:
# Aumentiamo i token massimi per contenere la risposta JSON
response = client.chat_completion(messages_classify, max_tokens=4096, temperature=0.1)
response_text = response.choices[0].message.content.strip()
# Pulisce la risposta per estrarre solo il JSON
json_start = response_text.find('[')
json_end = response_text.rfind(']') + 1
clean_json_str = response_text[json_start:json_end]
results = json.loads(clean_json_str)
except json.JSONDecodeError:
raise gr.Error(f"Il modello ha restituito un formato JSON non valido. Riprova. Risposta ricevuta: {response_text}")
except Exception as e:
raise gr.Error(f"Errore durante la classificazione in batch delle keyword: {e}")
df = pd.DataFrame(results)
csv_path = "classificazione_keyword.csv"
df.to_csv(csv_path, index=False)
return df, csv_path, f"**Contesto Rilevato:**\n{website_summary}"
# --- Interfaccia Grafica con Gradio (invariata) ---
with gr.Blocks(theme=gr.themes.Soft()) as demo:
gr.Markdown("# Strumento di Analisi Pertinenza Keyword")
gr.Markdown("Inserisci uno o più URL e una lista di keyword per classificarle in base al contenuto dei siti web.")
with gr.Row():
with gr.Column(scale=1):
urls_input = gr.Textbox(lines=5, label="URL da Analizzare", placeholder="https://www.esempio.it\nhttps://www.esempio.it/prodotti")
keywords_input = gr.Textbox(lines=15, label="Keyword da Classificare", placeholder="keyword 1\nkeyword 2\nkeyword 3...")
with gr.Column(scale=2):
summary_output = gr.Markdown("Il riassunto del sito apparirà qui...")
df_output = gr.DataFrame(headers=["Keyword", "Classificazione"], datatype=["str", "str"], label="Risultati Classificazione")
file_output = gr.File(label="Scarica Risultati in formato CSV")
analyze_button = gr.Button("Analizza Pertinenza", variant="primary")
analyze_button.click(
fn=classify_keywords,
inputs=[urls_input, keywords_input],
outputs=[df_output, file_output, summary_output]
)
demo.launch()