HomeBlogTranscribePlansAPI Keys

RAG su un canale YouTube: da RSS a risposte con citazioni

Pipeline da un feed RSS di un canale YouTube attraverso trascrizioni ed embedding fino a risposte con citazioni

Costruire una RAG su un canale YouTube significa rispondere a "cosa ha detto questo creator sull'argomento X, e in quale video?" — su tutto ciò che ha pubblicato. La pipeline è in quattro passi: scoprire i video via RSS, recuperare le trascrizioni, spezzare in chunk ed embeddare, rispondere con attribuzione. Ecco ogni passo in Python eseguibile, incluse le parti che quasi tutte le guide saltano: la gestione della quota e come appare onestamente una citazione senza timestamp.

Passo 1: ID dei video dal feed RSS del canale

YouTube pubblica ancora un vero feed RSS per ogni canale, senza chiave API:

import requests
import xml.etree.ElementTree as ET

CHANNEL_ID = "UCxxxxxxxxxxxxxxxxxxxxxx"
NS = "{http://www.youtube.com/xml/schemas/2015}"

feed = requests.get(
    f"https://www.youtube.com/feeds/videos.xml?channel_id={CHANNEL_ID}",
    timeout=30,
).text
video_ids = [el.text for el in ET.fromstring(feed).iter(f"{NS}videoId")]

Avvertenza: il feed restituisce solo i ~15 upload più recenti. Per un canale attivo, esegui questo codice su base pianificata e accumula gli ID in un file o in una tabella; il catalogo storico oltre quella soglia richiede la YouTube Data API o una lista manuale una tantum. Per tenere fresco un indice, però, l'RSS è perfetto — è anche la spina dorsale della pipeline da canale a newsletter.

Passo 2: recupera le trascrizioni, rispetta la quota

import time

API_KEY = "yt_your_key_here"

def fetch(video_id: str) -> dict | None:
    resp = requests.get(
        "https://youtube2text.org/api/transcribe",
        params={"url": f"https://www.youtube.com/watch?v={video_id}"},
        headers={"x-api-key": API_KEY},
        timeout=90,
    )
    if resp.status_code == 404:
        return None  # TRANSCRIPT_UNAVAILABLE — no captions, skip
    if resp.status_code == 429:
        time.sleep(resp.json()["error"].get("retryAfterSeconds", 30))
        return fetch(video_id)
    resp.raise_for_status()
    return resp.json()["result"]

transcripts = [t for vid in video_ids if (t := fetch(vid))]

Fai i conti sul tuo piano prima di iterare su un canale: il piano gratuito offre 5 video al mese, che coprono un proof of concept e nient'altro. Il basic (5,99 $) ne compra 50, il pro (9,99 $) ne compra 500 — il pro è la soglia realistica per indicizzare un canale più i suoi upload futuri.

Passo 3: chunk ed embedding

Qualsiasi vector store va bene; Chroma tiene corto l'esempio. I sottotitoli automatici spesso mancano di punteggiatura, quindi spezzo per numero di parole invece che per frasi:

import chromadb

def chunk(text: str, size: int = 300, overlap: int = 50):
    words = text.split()
    for i in range(0, len(words), size - overlap):
        yield " ".join(words[i:i + size])

client = chromadb.PersistentClient(path="./channel_index")
col = client.get_or_create_collection("channel")

for t in transcripts:
    for i, piece in enumerate(chunk(t["content"])):
        col.add(
            ids=[f'{t["videoId"]}-{i}'],
            documents=[piece],
            metadatas=[{"videoId": t["videoId"], "title": t["title"]}],
        )

Passo 4: rispondere con citazioni — versione onesta

La API restituisce testo semplice senza timestamp, quindi te lo dico chiaramente: da questi dati non puoi citare "al minuto 23:41". Quello che puoi fare — e che di solito basta — è l'attribuzione al video a livello di chunk: ogni chunk recuperato conosce il proprio videoId e title, quindi le risposte citano quale video l'ha detto, con un link funzionante.

hits = col.query(query_texts=["what's their take on electric trucks?"], n_results=5)

context = "\n\n".join(
    f'[{m["title"]}] (https://www.youtube.com/watch?v={m["videoId"]})\n{doc}'
    for doc, m in zip(hits["documents"][0], hits["metadatas"][0])
)
prompt = (
    "Answer using only the excerpts below. After each claim, "
    f"cite the video title in brackets.\n\n{context}\n\nQuestion: ..."
)

Passa prompt all'LLM che preferisci — anche un modello locale funziona benissimo qui, stessa configurazione della pipeline di riassunto con Ollama.

Note di assemblaggio

Per chi usa i framework: tutto questo si mappa uno a uno su LlamaIndex o LangChain, se preferisci non scrivere a mano il chunking. In ogni caso il livello delle trascrizioni è identico, ed è l'unica parte con un contatore sopra — prendi una chiave su youtube2text.org/app/keys e parti dal piano gratuito per validare la pipeline prima di puntarla contro 300 video.