Update app.py
Browse files
app.py
CHANGED
|
@@ -6,6 +6,8 @@ from youtube_comment_downloader import YoutubeCommentDownloader
|
|
| 6 |
from wordcloud import WordCloud
|
| 7 |
from langdetect import detect
|
| 8 |
import re
|
|
|
|
|
|
|
| 9 |
|
| 10 |
st.set_page_config(page_title="Análisis de Sentimientos en YouTube", layout="wide")
|
| 11 |
|
|
@@ -36,21 +38,33 @@ def get_comments(video_id, max_comments=50):
|
|
| 36 |
|
| 37 |
@st.cache_resource
|
| 38 |
def load_model(language):
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
if video_url:
|
| 45 |
video_id = extract_video_id(video_url)
|
| 46 |
|
| 47 |
if video_id:
|
| 48 |
-
|
| 49 |
st.image(f"https://img.youtube.com/vi/{video_id}/0.jpg", caption="Miniatura del video", width=480)
|
|
|
|
|
|
|
| 50 |
|
| 51 |
with st.spinner("🔍 Extrayendo comentarios..."):
|
| 52 |
comments = get_comments(video_id, max_comments=max_comments)
|
| 53 |
-
|
| 54 |
st.write(f"💬 Se extrajeron **{len(comments)}** comentarios")
|
| 55 |
|
| 56 |
if comments:
|
|
@@ -61,7 +75,7 @@ if video_url:
|
|
| 61 |
idioma = "desconocido"
|
| 62 |
st.markdown(f"🌐 **Idioma detectado:** `{idioma}`")
|
| 63 |
|
| 64 |
-
#
|
| 65 |
model = load_model(idioma)
|
| 66 |
|
| 67 |
with st.spinner("🧠 Analizando sentimientos..."):
|
|
@@ -79,25 +93,15 @@ if video_url:
|
|
| 79 |
|
| 80 |
df = pd.DataFrame({"Comentario": comments, "Sentimiento": sentiments})
|
| 81 |
|
| 82 |
-
#
|
| 83 |
-
st.subheader("📊 Resumen de Resultados")
|
| 84 |
sentiment_counts = df["Sentimiento"].value_counts()
|
| 85 |
total = sentiment_counts.sum()
|
| 86 |
dominant = sentiment_counts.idxmax()
|
| 87 |
-
st.markdown(f"**Sentimiento predominante:** `{dominant}`")
|
| 88 |
-
|
| 89 |
-
if dominant == "Positivo":
|
| 90 |
-
st.balloons()
|
| 91 |
-
elif dominant == "Negativo":
|
| 92 |
-
st.error("⚠️ Hay una gran cantidad de comentarios negativos.")
|
| 93 |
-
else:
|
| 94 |
-
st.info("🟡 Los comentarios son mayormente neutros.")
|
| 95 |
-
|
| 96 |
-
# 🎯 Predicción de éxito del video
|
| 97 |
pos = sentiment_counts.get("Positivo", 0)
|
| 98 |
neutro = sentiment_counts.get("Neutral", 0)
|
| 99 |
neg = sentiment_counts.get("Negativo", 0)
|
| 100 |
score = (pos + 0.5 * neutro) / total
|
|
|
|
| 101 |
st.metric("🎯 Probabilidad de éxito del video", f"{score*100:.1f} %")
|
| 102 |
if score > 0.75:
|
| 103 |
st.success("🚀 ¡Este video tiene alto potencial de viralidad!")
|
|
@@ -106,32 +110,39 @@ if video_url:
|
|
| 106 |
else:
|
| 107 |
st.warning("😐 El video podría no estar siendo bien recibido.")
|
| 108 |
|
| 109 |
-
#
|
| 110 |
-
st.
|
| 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 |
csv = df.to_csv(index=False).encode('utf-8')
|
| 136 |
st.download_button(
|
| 137 |
label="📥 Descargar resultados como CSV",
|
|
|
|
| 6 |
from wordcloud import WordCloud
|
| 7 |
from langdetect import detect
|
| 8 |
import re
|
| 9 |
+
import requests
|
| 10 |
+
from bs4 import BeautifulSoup
|
| 11 |
|
| 12 |
st.set_page_config(page_title="Análisis de Sentimientos en YouTube", layout="wide")
|
| 13 |
|
|
|
|
| 38 |
|
| 39 |
@st.cache_resource
|
| 40 |
def load_model(language):
|
| 41 |
+
return pipeline("sentiment-analysis", model="nlptown/bert-base-multilingual-uncased-sentiment")
|
| 42 |
+
|
| 43 |
+
@st.cache_resource
|
| 44 |
+
def load_summarizer():
|
| 45 |
+
return pipeline("summarization", model="sshleifer/distilbart-cnn-12-6")
|
| 46 |
+
|
| 47 |
+
def get_video_title(video_id):
|
| 48 |
+
url = f"https://www.youtube.com/watch?v={video_id}"
|
| 49 |
+
response = requests.get(url)
|
| 50 |
+
soup = BeautifulSoup(response.text, "html.parser")
|
| 51 |
+
title_tag = soup.find("title")
|
| 52 |
+
if title_tag:
|
| 53 |
+
return title_tag.text.replace("- YouTube", "").strip()
|
| 54 |
+
return "Título no encontrado"
|
| 55 |
|
| 56 |
if video_url:
|
| 57 |
video_id = extract_video_id(video_url)
|
| 58 |
|
| 59 |
if video_id:
|
| 60 |
+
title = get_video_title(video_id)
|
| 61 |
st.image(f"https://img.youtube.com/vi/{video_id}/0.jpg", caption="Miniatura del video", width=480)
|
| 62 |
+
st.markdown(f"### 📺 {title}")
|
| 63 |
+
st.markdown(f"[🔗 Ver en YouTube](https://www.youtube.com/watch?v={video_id})")
|
| 64 |
|
| 65 |
with st.spinner("🔍 Extrayendo comentarios..."):
|
| 66 |
comments = get_comments(video_id, max_comments=max_comments)
|
| 67 |
+
|
| 68 |
st.write(f"💬 Se extrajeron **{len(comments)}** comentarios")
|
| 69 |
|
| 70 |
if comments:
|
|
|
|
| 75 |
idioma = "desconocido"
|
| 76 |
st.markdown(f"🌐 **Idioma detectado:** `{idioma}`")
|
| 77 |
|
| 78 |
+
# Modelo de sentimiento
|
| 79 |
model = load_model(idioma)
|
| 80 |
|
| 81 |
with st.spinner("🧠 Analizando sentimientos..."):
|
|
|
|
| 93 |
|
| 94 |
df = pd.DataFrame({"Comentario": comments, "Sentimiento": sentiments})
|
| 95 |
|
| 96 |
+
# 🎯 Predicción de éxito del video
|
|
|
|
| 97 |
sentiment_counts = df["Sentimiento"].value_counts()
|
| 98 |
total = sentiment_counts.sum()
|
| 99 |
dominant = sentiment_counts.idxmax()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
pos = sentiment_counts.get("Positivo", 0)
|
| 101 |
neutro = sentiment_counts.get("Neutral", 0)
|
| 102 |
neg = sentiment_counts.get("Negativo", 0)
|
| 103 |
score = (pos + 0.5 * neutro) / total
|
| 104 |
+
|
| 105 |
st.metric("🎯 Probabilidad de éxito del video", f"{score*100:.1f} %")
|
| 106 |
if score > 0.75:
|
| 107 |
st.success("🚀 ¡Este video tiene alto potencial de viralidad!")
|
|
|
|
| 110 |
else:
|
| 111 |
st.warning("😐 El video podría no estar siendo bien recibido.")
|
| 112 |
|
| 113 |
+
# 🧠 Resumen automático (usando comentarios)
|
| 114 |
+
with st.spinner("📝 Generando resumen..."):
|
| 115 |
+
summarizer = load_summarizer()
|
| 116 |
+
resumen_input = " ".join(df["Comentario"].tolist())[:1000] # limit input size
|
| 117 |
+
resumen = summarizer(resumen_input, max_length=60, min_length=15, do_sample=False)[0]['summary_text']
|
| 118 |
+
st.subheader("🧠 Resumen automático de los comentarios")
|
| 119 |
+
st.markdown(f"_{resumen}_")
|
| 120 |
+
|
| 121 |
+
# 🧱 Organización con pestañas
|
| 122 |
+
tab1, tab2, tab3 = st.tabs(["📊 Análisis", "☁️ Palabras", "📌 Comentarios clave"])
|
| 123 |
+
|
| 124 |
+
with tab1:
|
| 125 |
+
st.dataframe(df)
|
| 126 |
+
fig1, ax1 = plt.subplots()
|
| 127 |
+
sentiment_counts.plot(kind='pie', autopct='%1.1f%%', ax=ax1, ylabel="")
|
| 128 |
+
ax1.set_title("Distribución de Sentimientos")
|
| 129 |
+
st.pyplot(fig1)
|
| 130 |
+
|
| 131 |
+
with tab2:
|
| 132 |
+
all_text = " ".join(df["Comentario"])
|
| 133 |
+
wordcloud = WordCloud(width=800, height=400, background_color="white").generate(all_text)
|
| 134 |
+
fig_wc, ax_wc = plt.subplots()
|
| 135 |
+
ax_wc.imshow(wordcloud, interpolation='bilinear')
|
| 136 |
+
ax_wc.axis("off")
|
| 137 |
+
st.pyplot(fig_wc)
|
| 138 |
+
|
| 139 |
+
with tab3:
|
| 140 |
+
for tipo in ["Positivo", "Neutral", "Negativo"]:
|
| 141 |
+
ejemplo = df[df["Sentimiento"] == tipo].head(1)
|
| 142 |
+
if not ejemplo.empty:
|
| 143 |
+
st.markdown(f"**{tipo}**: _{ejemplo['Comentario'].values[0]}_")
|
| 144 |
+
|
| 145 |
+
# Descargar CSV
|
| 146 |
csv = df.to_csv(index=False).encode('utf-8')
|
| 147 |
st.download_button(
|
| 148 |
label="📥 Descargar resultados como CSV",
|