Resumen
Este flujo de trabajo collects YouTube comentarios diario for monitored canales y videos, puntuaciones them for sentiment, y surfaces notable menciones, complaints, y caracteristica solicitudes. It replaces manual YouTube Studio verifica con un automatizado pipeline ese catches importante audiencia retroalimentacion dentro de 24 horas.
Desencadenador
Cron programar (diario at 9 AM UTC)
Programación
Ejecuta diario at 9 AM UTC
Pasos del flujo de trabajo
Cargar monitored canales y palabras clave
Leer el lista of canales, video URLs, y marca palabras clave to monitorear de configuracion.
Search for reciente videos
Consulta YouTube via Scavio for reciente videos de monitored canales y palabras clave.
Extraer comentario datos
Pull structured comentario datos including text, author, marca de tiempo, y like conteo.
Puntuacion sentiment
Apply basico keyword-based sentiment scoring to categorizar comentarios as positive, negative, o neutral.
Marcar notable comentarios
Surface comentarios con high engagement, menciones de marca, o negative sentiment for resena.
Generar diario digest
Compile flagged comentarios en un digest y enviar to Slack o correo electronico.
Implementacion en Python
import requests
import json
from pathlib import Path
from datetime import datetime
API_KEY = "your_scavio_api_key"
POSITIVE_WORDS = ["love", "great", "amazing", "helpful", "best", "awesome", "excellent"]
NEGATIVE_WORDS = ["bad", "terrible", "broken", "hate", "worst", "scam", "awful", "disappointed"]
def search_youtube(query: str) -> list[dict]:
res = requests.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": API_KEY},
json={"platform": "youtube", "query": query},
timeout=15,
)
res.raise_for_status()
return res.json().get("organic", [])
def score_sentiment(text: str) -> str:
lower = text.lower()
pos = sum(1 for w in POSITIVE_WORDS if w in lower)
neg = sum(1 for w in NEGATIVE_WORDS if w in lower)
if neg > pos:
return "negative"
if pos > neg:
return "positive"
return "neutral"
def extract_comments(videos: list[dict]) -> list[dict]:
comments = []
for video in videos:
for comment in video.get("comments", []):
sentiment = score_sentiment(comment.get("text", ""))
comments.append({
"video_title": video.get("title", ""),
"author": comment.get("author", ""),
"text": comment.get("text", ""),
"likes": comment.get("likes", 0),
"sentiment": sentiment,
"video_link": video.get("link", ""),
})
return comments
def run():
config = json.loads(Path("monitor_config.json").read_text())
keywords = config.get("keywords", ["your brand name"])
all_comments = []
for kw in keywords:
videos = search_youtube(kw)
comments = extract_comments(videos)
all_comments.extend(comments)
# Flag notable comments
notable = [c for c in all_comments if c["sentiment"] == "negative" or c["likes"] >= 10]
notable.sort(key=lambda x: x["likes"], reverse=True)
date = datetime.utcnow().strftime("%Y-%m-%d")
report = {
"date": date,
"total_comments": len(all_comments),
"positive": sum(1 for c in all_comments if c["sentiment"] == "positive"),
"negative": sum(1 for c in all_comments if c["sentiment"] == "negative"),
"neutral": sum(1 for c in all_comments if c["sentiment"] == "neutral"),
"notable": notable[:20],
}
Path(f"yt_comments_{date}.json").write_text(json.dumps(report, indent=2))
print(f"Collected {len(all_comments)} comments, {len(notable)} notable")
for c in notable[:5]:
print(f" [{c['sentiment']}] {c['text'][:80]}")
if __name__ == "__main__":
run()Implementacion en JavaScript
const API_KEY = "your_scavio_api_key";
const POSITIVE_WORDS = ["love", "great", "amazing", "helpful", "best", "awesome", "excellent"];
const NEGATIVE_WORDS = ["bad", "terrible", "broken", "hate", "worst", "scam", "awful", "disappointed"];
async function searchYouTube(query) {
const res = await fetch("https://api.scavio.dev/api/v1/search", {
method: "POST",
headers: { "x-api-key": API_KEY, "content-type": "application/json" },
body: JSON.stringify({ platform: "youtube", query }),
});
if (!res.ok) throw new Error(`scavio ${res.status}`);
return (await res.json()).organic ?? [];
}
function scoreSentiment(text) {
const lower = text.toLowerCase();
const pos = POSITIVE_WORDS.filter((w) => lower.includes(w)).length;
const neg = NEGATIVE_WORDS.filter((w) => lower.includes(w)).length;
if (neg > pos) return "negative";
if (pos > neg) return "positive";
return "neutral";
}
function extractComments(videos) {
const comments = [];
for (const video of videos) {
for (const comment of video.comments ?? []) {
comments.push({
videoTitle: video.title ?? "",
author: comment.author ?? "",
text: comment.text ?? "",
likes: comment.likes ?? 0,
sentiment: scoreSentiment(comment.text ?? ""),
videoLink: video.link ?? "",
});
}
}
return comments;
}
async function run() {
const fs = await import("fs/promises");
const config = JSON.parse(await fs.readFile("monitor_config.json", "utf8"));
const keywords = config.keywords ?? ["your brand name"];
const allComments = [];
for (const kw of keywords) {
const videos = await searchYouTube(kw);
allComments.push(...extractComments(videos));
}
const notable = allComments
.filter((c) => c.sentiment === "negative" || c.likes >= 10)
.sort((a, b) => b.likes - a.likes);
const date = new Date().toISOString().slice(0, 10);
const report = {
date,
totalComments: allComments.length,
positive: allComments.filter((c) => c.sentiment === "positive").length,
negative: allComments.filter((c) => c.sentiment === "negative").length,
neutral: allComments.filter((c) => c.sentiment === "neutral").length,
notable: notable.slice(0, 20),
};
await fs.writeFile(`yt_comments_${date}.json`, JSON.stringify(report, null, 2));
console.log(`Collected ${allComments.length} comments, ${notable.length} notable`);
for (const c of notable.slice(0, 5)) console.log(` [${c.sentiment}] ${c.text.slice(0, 80)}`);
}
run();Plataformas utilizadas
YouTube
Búsqueda de videos con transcripciones y metadatos