问答

怎么用 YouTube 视频字幕做一个 RAG 问答机器人?

简短回答

先用 YouTube 视频搜索找到要收录的视频(每页约 20 条,$0.004),再逐个拉取带时间戳的字幕(每个视频 $0.004),按时间窗口切块后存进任意向量库。收录 200 个视频大约花 $0.84,每个回答都能链接到出处的那一秒。

一家做健身教练培训的公司,自己有 200 个长视频,都是教练在回答学员问题,另外还想收进同行里讲得最好的一批演讲。他们要的机器人是这样的:有人问大重量深蹲前怎么热身,它用两句话回答,再给一个链接,点开直接跳到某个视频的 14:32。这就是一条 RAG 流水线,只多一个讲究:时间戳要从头保留到尾,因为一个能跳到那一刻的链接,比一段总结更有用。

整体流程

  1. 收集视频 ID。 用 YouTube 视频搜索按主题关键词搜,或者用频道名加主题词搜。目前没有列出某个频道全部视频的接口,自己的频道可以直接从 YouTube Studio 里复制视频 ID。
  2. 拉字幕。 YouTube 视频字幕接收 11 位视频 ID 和可选的 lang,返回 segments(开始时间、结束时间、文字)和完整的 text。
  3. 按时间切块。 把相邻片段合并成 60 到 90 秒的窗口,每块都带上 video_id、标题和窗口的起始秒数。
  4. 向量化入库。 用你现成的工具就行:LlamaIndex、LangChain、pgvector、Qdrant 都可以。
  5. 回答带出处。 用块里的元数据拼出 https://youtu.be/<id>?t=<起始秒>,要求模型在回答里引用。

第 1 到 3 步的 Python 代码

import json, os, requests

API = "https://api.okdata.online"
H = {"Authorization": f"Bearer {os.environ['OKDATA_API_KEY']}"}

def get(path, **params):
    body = requests.get(API + path, headers=H, params=params, timeout=90).json()
    if body["code"] != "ok":
        raise RuntimeError(f"{body['code']}: {body['message']}")
    return body["data"]

videos = get("/v1/youtube/videos/search", query="squat warm up coaching",
             fields="id,title,url")["items"]

with open("chunks.jsonl", "w", encoding="utf-8") as out:
    for v in videos:
        try:
            tr = get("/v1/youtube/videos/transcript/get", id=v["id"], lang="en")["item"]
        except RuntimeError as err:  # 没字幕、私密、已删除:都不扣费
            print("跳过", v["id"], err)
            continue
        window, start = [], None
        for seg in tr["segments"]:
            start = seg["start"] if start is None else start
            window.append(seg["text"])
            if seg["end"] - start >= 75:
                out.write(json.dumps({"video_id": v["id"], "title": v["title"], "start": int(start),
                                      "text": " ".join(window)}, ensure_ascii=False) + "\n")
                window, start = [], None
        if window:
            out.write(json.dumps({"video_id": v["id"], "title": v["title"], "start": int(start),
                                  "text": " ".join(window)}, ensure_ascii=False) + "\n")

chunks.jsonl 里的每一行都可以直接拿去做向量化。

收录要花多少

知识库规模 调用次数 费用
50 个视频 搜 3 次 + 50 份字幕 {{cost:youtube.videos.search3+youtube.videos.transcript.get50}}
200 个视频 搜 10 次 + 200 份字幕 {{cost:youtube.videos.search10+youtube.videos.transcript.get200}}
1,000 个视频 搜 50 次 + 1,000 份字幕 {{cost:youtube.videos.search50+youtube.videos.transcript.get1000}}

更新比新建便宜得多:每月跑一次,只给还没入库的视频 ID 拉字幕。

容易出问题的地方

  • 自动生成的字幕常把人名和术语听错。在回答的提示词里放一份术语表,或者入库前先替换掉。
  • 没有字幕的视频会调用失败,不扣费。这些视频如果重要,要另外对音频做转写。
  • 用 lang 指定你入库的语言,一个索引里混着几种语言会拖累检索效果。
  • 字幕的版权属于创作者。内部用来答疑问题不大,原样转载发布就是另一回事了。

其他做法

LlamaIndex 的 YoutubeTranscriptReader 和 LangChain 的 YoutubeLoader 免费,底层都是 youtube-transcript-api。在自己电脑上跑效果不错,但在云服务器和 Serverless 环境里,它们的 issue 里有很多被封 IP、拿不到字幕的反馈。如果是你自己的频道,YouTube Studio 和 YouTube Data API 能拿到你上传过的字幕。完全没有字幕的视频,可以下载音频用 Whisper 转写,代价是下载和算力。这类需求的更多做法见视频转文字方案。

更新于