最近在整理 B 站收藏夹时,发现很多优质教程、技术分享和课程视频都存在一个共性痛点:视频没法直接搜索、没法快速定位知识点、没法把多个视频的内容串联起来做横向对比。想引用其中的某个观点,只能靠记忆硬翻弹幕和评论区。针对这个问题,我梳理了一套完整的“B站视频转文字 + 多P批量处理 + 跨视频AI问答 + 角标溯源”的落地方案,本文会结合谛听AI的能力,从技术原理到工程实现逐步拆解,帮助你把收藏夹变成可检索、可问答、可溯源的个人知识库。
1. 视频转文字与AI问答知识库,到底能解决什么问题
1.1 收藏夹里的视频,为什么难以直接利用
B站聚集了大量优质学习资源,尤其是编程教程、框架源码解析、系统设计公开课和行业分享。但视频这种载体有一个天然劣势:它是线性播放的,内容无法被搜索引擎直接索引。当你需要回顾某个知识点时,只能凭记忆找到大概的分P位置,然后拖进度条反复试看;当你需要横向对比两个视频中关于同一问题的讲解时,更是要来回切换窗口。
这个痛点本质上是因为视频内容处于“不可检索的暗数据”状态。要让视频真正变成知识库,第一步就是把它转换成结构化程度更高的文本形式。
1.2 视频转文字、AI问答、角标溯源分别是什么
视频转文字是指利用语音识别技术(ASR,Automatic Speech Recognition)将视频中的语音内容转成文字。常见的输出形式包括纯文本字幕、SRT字幕文件、带时间戳的分段文本等。
AI问答是建立在文字内容之上的能力。将转写出来的文本按语义切分成片段,通过向量化处理存入向量数据库,再结合大语言模型(LLM)实现针对视频内容的自动问答。例如你可以直接问系统“这个视频里作者提到了哪三种优化方法”,系统会从文字库中检索到相关片段并生成回答。
角标溯源是知识库应用中很重要的一环。它要求AI在回答问题时,不仅要给出结论,还要标注这段结论来自哪个视频的哪一分P、在什么时间点出现。这样做有两个好处:一是回答结果可以复核,避免大模型编造内容;二是用户可以快速跳转到原视频对应位置,查看完整上下文。
1.3 这套方案适用的典型场景
- 学习笔记整理:把课程视频转成文字稿,建立个人笔记库,支持关键字搜索。
- 多视频横向对比:同一主题下多个UP主的观点差异,通过AI问答快速汇总。
- 团队知识沉淀:把培训录像、内部技术分享视频转成可检索的知识资产。
- 论文/文献辅助:对学术讲座视频进行内容抽取和重点标记。
- 内容二次创作:从视频中快速提取素材片段,定位到具体时间点。
2. 谛听AI的核心能力与技术架构
2.1 谛听AI在整套流程中的位置
谛听AI是一套面向视频内容处理与知识库构建的整合方案。它把“视频获取、批量转写、知识库构建、AI问答”这条原本需要人工串联多个工具才能完成的链路,整合成了可视化、可配置的流水线。相比自己从零搭建,谛听AI在视频批量处理、长视频分段、多P自动识别、答案溯源等方面做了大量工程化处理。
从接口设计上看,它的使用思路一般是:先在网页端或客户端中配置视频来源(可以是B站链接或本地文件),系统自动拉取视频信息并拆分多P,然后依次完成转写、文本清洗、向量入库等步骤。用户最终面对的是一个类似“聊天窗口”的交互界面,可以在里面针对已入库的全部视频进行提问。
2.2 底层技术栈拆解
要实现“B站视频转文字 + 多P批量 + 跨视频AI问答 + 角标溯源”这套能力,背后依赖以下技术模块:
| 功能模块 | 核心技术 | 输出产物 |
|---|---|---|
| 视频获取 | bilibili API / yt-dlp | MP4/M4A 视频文件 |
| 音频抽取 | FFmpeg | 16kHz WAV/M4A 音频 |
| 语音识别 | Whisper / FunASR / 商业ASR | SRT、VTT、JSON 时间轴字幕 |
| 文本切片 | 语义分块算法 | 带元数据文本片段 |
| 向量化 | Embedding 模型 | 向量索引 |
| 向量存储 | Chroma / Milvus / FAISS | 向量数据库 |
| 问答生成 | 大语言模型 + RAG | 答案 + 引用列表 |
| 溯源展示 | 时间轴映射 | 视频链接 + 分P + 时间戳 |
2.3 为什么需要 RAG(检索增强生成)而不直接让大模型回答
如果你把全部转写文本一次性丢给大模型,可能会有几个问题:
第一是上下文窗口限制。一个1小时视频的转写文字大约有1.5万到2万字,多个视频拼接后动辄数十万字,远超常见大模型的单次处理长度。
第二是召回精度问题。没有检索机制的大模型回答,本质上是“凭训练记忆作答”,无法保证内容确实来自你指定的视频集合。
第三是溯源困难。直接生成回答无法自动给出“这句话出自哪一P的哪一分钟”。
RAG(检索增强生成)的思路是:把文本按照语义切块,向量化存储;提问时,先对用户问题做向量化,在库中检索最相关的若干片段,再把这些片段作为上下文拼接到提示词中,交给大模型生成回答。这样既规避了上下文窗口限制,又能保证回答基于真实来源,同时可以在返回片段中携带时间戳信息,实现角标溯源。
3. 环境准备与基础工具链
3.1 运行环境建议
由于语音识别模型对计算资源有一定要求,建议按下面的组合准备环境:
- 操作系统:Windows 10/11、Ubuntu 20.04+、macOS 12+ 均可。
- Python 版本:3.9 或 3.10 以上,推荐使用 conda 创建独立环境,避免依赖冲突。
- FFmpeg:必须安装,用于音频抽取和格式转换。
- 硬件:如果使用本地方案跑 Whisper,建议 NVIDIA 显卡 8GB 显存以上;没有显卡时也可以使用 CPU 跑,但速度会慢很多。也可以直接使用在线ASR接口避免本地资源占用。
版本需要根据你的项目实际情况调整,文中示例以常见环境为例,重点演示配置思路。
3.2 安装 Python 依赖
创建独立环境并安装核心依赖:
conda create -n bili-rag python=3.10 -y conda activate bili-rag安装视频下载工具:
pip install yt-dlp安装FFmpeg(Ubuntu示例):
sudo apt update && sudo apt install ffmpeg -yWindows 用户可以从 FFmpeg 官网下载编译好的二进制包,配置环境变量后即可使用。macOS 用户可以使用 Homebrew:
brew install ffmpeg安装语音识别工具。如果选择 OpenAI Whisper:
pip install openai-whisper如果选择FunASR(更适合中文长音频场景):
pip install funasr modelscope安装向量数据库和LLM相关依赖:
pip install chromadb openai langchain这里需要说明的是,如果你使用谛听AI的产品化流程,可以跳过本地环境搭建,直接在平台上操作。但是了解底层工具链仍然很有价值,因为在实际项目中,你可能会遇到平台不能满足的定制化需求。
4. 实战:B站视频多P批量下载与转文字
4.1 理解B站多P视频的下载逻辑
B站的多P视频,也就是一个视频页包含多个分P,常见于长课程、系列教程和直播录播。下载多P视频时,不能只拿第一P,而需要遍历所有分P的信息,逐个拉取视频流。
使用 yt-dlp 下载B站视频时,可以通过以下命令查看视频列表信息:
yt-dlp --flat-playlist --print "%(playlist_index)s %(title)s %(id)s" "https://www.bilibili.com/video/BVxxxxxx"这个命令会输出该视频下所有分P的序号、标题和视频ID。确认列表无误后,执行批量下载:
yt-dlp -f "bv*[height<=1080]+ba/b" --write-auto-subs --sub-langs "zh-Hans" -o "./videos/%(playlist_index)02d_%(title)s.%(ext)s" "https://www.bilibili.com/video/BVxxxxxx"参数解释:
-f "bv*[height<=1080]+ba/b":选择最高不超过1080P的视频流和音频流。--write-auto-subs --sub-langs "zh-Hans":自动下载中文CC字幕,如果有的话。-o:指定输出文件名格式,%(playlist_index)02d会把分P序号补零成两位数,便于排序。
下载完成后,检查文件列表:
ls -lh videos/预期看到以下格式的文件:
videos/ 01_第一章 认识大模型.mp4 02_第二章 提示词工程.mp4 03_第三章 RAG实战.mp44.2 批量抽取音频
语音识别通常不直接处理视频文件,而是先把音频抽取出来,转换为模型更友好的采样率和声道格式。使用FFmpeg批量抽取音频的脚本如下:
#!/bin/bash # 文件路径:extract_audio.sh for video in videos/*.mp4; do filename=$(basename "$video" .mp4) ffmpeg -i "$video" -vn -ac 1 -ar 16000 "audios/${filename}.wav" -y done这段脚本遍历videos/目录下所有视频文件,用FFmpeg抽取单声道、16kHz采样率的WAV音频。这里采样率选择16000Hz是语音识别任务的常见做法,既保留了足够的语音信息,又比44.1kHz节省空间和处理时间。
抽取后的文件结构:
audios/ 01_第一章 认识大模型.wav 02_第二章 提示词工程.wav 03_第三章 RAG实战.wav4.3 使用 Whisper 批量转写
进入Python环境,编写一个批量转写脚本。Whisper支持多种模型规格,中文场景下推荐使用medium或large-v3,英文内容可以适当下调规格以提升速度。
# 文件路径:transcribe.py import whisper import os import json model = whisper.load_model("medium") input_dir = "audios" output_dir = "transcripts" os.makedirs(output_dir, exist_ok=True) audio_files = sorted(os.listdir(input_dir)) for audio_file in audio_files: if not audio_file.endswith(".wav"): continue audio_path = os.path.join(input_dir, audio_file) output_path = os.path.join(output_dir, audio_file.replace(".wav", ".json")) print(f"正在转写:{audio_file}") result = model.transcribe(audio_path, language="zh", verbose=False) with open(output_path, "w", encoding="utf-8") as f: json.dump(result, f, ensure_ascii=False, indent=2) print(f"完成:{audio_file},共 {len(result['segments'])} 个片段")model.transcribe返回的结果中包含segments列表,每个segment包含start、end、text等字段。这些时间戳信息最终会被用于角标溯源。
4.4 将转写结果整理为带时间轴的结构化文本
Whisper输出的JSON格式适合程序读取,但不利于后续向量化和人工阅读。建议将每个视频的转写结果转换为一个统一的中间JSON结构:
# 文件路径:build_corpus.py import json import os transcript_dir = "transcripts" corpus = [] for transcript_file in sorted(os.listdir(transcript_dir)): if not transcript_file.endswith(".json"): continue with open(os.path.join(transcript_dir, transcript_file), "r", encoding="utf-8") as f: data = json.load(f) video_title = transcript_file.replace(".json", "") for segment in data.get("segments", []): corpus.append({ "video": video_title, "start": segment["start"], "end": segment["end"], "text": segment["text"].strip() }) with open("corpus.json", "w", encoding="utf-8") as f: json.dump(corpus, f, ensure_ascii=False, indent=2) print(f"共构建 {len(corpus)} 个文本片段")这个结构非常关键:每个片段都保留了视频标题、开始时间、结束时间、文本内容,这就是后面做跨视频AI问答和角标溯源的数据基础。
5. 实战:跨视频知识库构建与AI问答
5.1 文本清洗与语义切片
Whisper转写结果中经常包含语气词、重复词、句子中断等问题。在向量化之前需要做两步处理。
第一步是清洗。对于中文文本,可以过滤掉“嗯”“啊”“那个”“这个”等无意义词汇。这里使用简单规则处理:
# 文件路径:clean_text.py import re def clean_text(text: str) -> str: # 去除多余空格和换行 text = re.sub(r"\s+", " ", text) # 去除常见语气词和填充词 text = re.sub(r"^(嗯|啊|哦|那|这个|那个|就是说|然后)+", "", text) return text.strip()第二步是切片。Whisper的segment本身可能比较短,直接做向量化会导致检索粒度太碎。建议将多个段落按语义聚合为长度适中的片段。常见做法是设置目标片段长度为500个字符左右,并按段落边界滑动切分:
# 文件路径:chunk_text.py def segment_corpus(corpus, max_chars=500): chunks = [] current_text = "" current_video = None current_start = None current_end = None for item in corpus: text = item["text"] if current_video is None: current_video = item["video"] current_start = item["start"] current_end = item["end"] elif item["video"] != current_video: # 视频切换,先保存当前片段 if current_text.strip(): chunks.append({ "video": current_video, "start": current_start, "end": current_end, "text": current_text.strip() }) current_video = item["video"] current_text = text current_start = item["start"] current_end = item["end"] else: current_end = item["end"] current_text += " " + text if len(current_text) >= max_chars: chunks.append({ "video": current_video, "start": current_start, "end": current_end, "text": current_text.strip() }) current_text = "" current_video = None if current_text.strip(): chunks.append({ "video": current_video, "start": current_start, "end": current_end, "text": current_text.strip() }) return chunks这个切片策略保证了同一个片段尽量来自同一个视频的连续区域,同时在片段中保留了视频来源和时间轴信息。
5.2 向量化并写入向量数据库
向量化是把文本变成高维数值向量的过程。这里使用 OpenAI Embedding 接口作为示例,实际项目中也可以替换为开源模型如bge-large-zh或text2vec-large-chinese,避免依赖外部API。
# 文件路径:vectorize.py from openai import OpenAI import chromadb import json client = OpenAI() # 读取切片后的语料 with open("chunks.json", "r", encoding="utf-8") as f: chunks = json.load(f) # 初始化Chroma向量库 chroma_client = chromadb.PersistentClient(path="./vector_store") collection = chroma_client.get_or_create_collection("bili_knowledge") # 为每个片段生成向量并入库 for i, chunk in enumerate(chunks): response = client.embeddings.create( model="text-embedding-3-small", input=chunk["text"] ) embedding = response.data[0].embedding collection.add( ids=[str(i)], embeddings=[embedding], documents=[chunk["text"]], metadatas=[{ "video": chunk["video"], "start": chunk["start"], "end": chunk["end"] }] ) print(f"已写入 {len(chunks)} 条向量数据")这里需要特别注意:元数据(metadata)是溯源的关键。video、start、end三个字段在后续问答中会被提取出来,生成带角标的回答。
5.3 构建RAG问答链路
RAG问答链路的逻辑可以拆成四步:
- 对用户问题做向量化。
- 在向量数据库中检索最相近的 Top-K 片段,K值通常取3到5。
- 将片段文本拼接到提示词中,连同问题一起发送给大模型。
- 解析大模型回答,并将命中的片段信息转换为可见的引用角标。
下面是完整的问答函数:
# 文件路径:qa_engine.py from openai import OpenAI import chromadb client = OpenAI() chroma_client = chromadb.PersistentClient(path="./vector_store") collection = chroma_client.get_collection("bili_knowledge") def ask_knowledge_base(question: str, top_k: int = 5): # 1. 向量化用户问题 q_embedding = client.embeddings.create( model="text-embedding-3-small", input=question ).data[0].embedding # 2. 检索相似片段 results = collection.query( query_embeddings=[q_embedding], n_results=top_k, include=["documents", "metadatas"] ) documents = results["documents"][0] metadatas = results["metadatas"][0] # 3. 拼接上下文提示词 context_parts = [] for idx, (doc, meta) in enumerate(zip(documents, metadatas)): video = meta["video"] start = meta["start"] context_parts.append(f"[{idx}] 来源:{video},时间点 {start:.1f}秒\n{doc}") context = "\n\n".join(context_parts) prompt = f"""你是一个基于视频知识库的问答助手。请根据以下检索到的视频片段回答问题。 每个片段前有编号和来源信息,回答时请用[编号]标注你引用的来源。 搜索到的片段: {context} 用户问题:{question} 请给出简洁准确的回答,并在句子末尾用[编号]标注引用来源。""" # 4. 调用大模型生成回答 response = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": "你是一个严谨的知识库问答助手,回答必须基于给定片段,不许编造。"}, {"role": "user", "content": prompt} ], temperature=0.3 ) answer = response.choices[0].message.content # 生成溯源列表 sources = [] for idx, (doc, meta) in enumerate(zip(documents, metadatas)): sources.append({ "ref_id": idx, "video": meta["video"], "start": meta["start"], "end": meta["end"], "snippet": doc[:80] }) return answer, sources这段代码的Prompt设计是关键。通过在上下文片段中加入编号和来源信息,并明确要求模型在回答时标注引用来源,就能实现“角标溯源”的输出效果。
5.4 运行与验证
现在来测试整个问答链路:
# 文件路径:test_qa.py from qa_engine import ask_knowledge_base answer, sources = ask_knowledge_base("这个课程里提到了哪几种提升RAG检索效果的方法?") print("AI回答:") print(answer) print("\n引用来源:") for source in sources: print(f"[{source['ref_id']}] {source['video']} @ {source['start']:.1f}s")预期输出大致如下:
AI回答: 根据课程内容,提升RAG检索效果的方法主要有三种:优化文档切分策略、引入重排序模型(Reranker)、以及改进向量检索时的混合检索方式。[1][3] 引用来源: [1] 03_第三章 RAG实战 @ 120.3s [3] 05_第五章 高级检索策略 @ 45.8s注意这里模型编号引用与sources列表对齐。用户看到回答后,可以直接点击引用来源跳到原视频对应时间点。
5.5 将时间戳转换为B站跳转链接
为了让角标溯源真正可用,可以把时间戳转换为B站视频的跳转链接。B站URL支持带参数跳转到指定时间点,格式为https://www.bilibili.com/video/{bvid}?p={part}&t={seconds}。
这里需要维护一个分P序号到视频链接的映射:
# 文件路径:build_links.py mapping = { "01_第一章 认识大模型": {"bvid": "BVxxxxxx", "part": 1}, "02_第二章 提示词工程": {"bvid": "BVxxxxxx", "part": 2}, "03_第三章 RAG实战": {"bvid": "BVxxxxxx", "part": 3}, } def build_video_url(video_name: str, timestamp: float) -> str: info = mapping.get(video_name) if not info: return "" return f"https://www.bilibili.com/video/{info['bvid']}?p={info['part']}&t={int(timestamp)}"把build_video_url集成到问答结果输出中,用户点开链接就能直达对应分P的时间点,这样就完成了从“提问”到“答案”再到“原视频验证”的闭环。
6. 常见问题与排查思路
6.1 高频问题速查表
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| yt-dlp 下载B站视频失败 | B站接口变动、需要登录态或 Cookie | 升级 yt-dlp;添加--cookies-from-browser chrome参数 |
| 下载的视频没有声音 | 音频流选择错误 | 检查-f参数,确保包含ba音频流 |
| Whisper 转写速度极慢 | 使用CPU推理且视频较长 | 改用GPU;切换small模型;使用FunASR在线推理接口 |
| 中文转写错误率高 | 模型规格过低,或专有名词多 | 使用large-v3;在 Prompt 中补充术语表 |
| 时间戳与视频内容对不上 | 抽取音频前做了转码导致时间偏移 | 尽量直接从原视频抽取音频,不要先裁剪或变速 |
| 向量检索命中不相关片段 | 切片过长导致语义混杂 | 调整 max_chars 到300-500;尝试按段落边界切分 |
| 大模型回答不是来自视频内容 | Prompt 未约束,或检索片段未命中 | 加强Prompt约束;增大 top_k;开启低 temperature 参数 |
| 多个视频内容互相干扰 | 知识库混合了不同主题的视频 | 按主题建立不同Collection;或给元数据添加“主题”字段 |
6.2 定位“答案不对”的根本原因
如果AI回答的内容明显错误,不要急着调Prompt。先检查召回环节:
首先,打印检索到的原始片段,确认片段文本本身是否相关:
results = collection.query( query_embeddings=[q_embedding], n_results=5, include=["documents", "metadatas"] ) for doc in results["documents"][0]: print(doc)如果检索结果中就没有出现正确答案,说明问题出在切片或向量化环节,而不是大模型。此时需要:
- 检查文本清洗是否过度剔除关键内容。
- 检查切片长度是否合理,是否把关键信息从上下文中切断。
- 检查Embedding模型对中文长文本的支持情况。
如果检索结果中明明有相关片段,但大模型回答还是错了,说明Prompt约束不够,需要更明确地告诉模型“只能使用给定片段中的信息回答”。
6.3 显存不足的应对策略
本地跑Whisper时常见的报错是CUDA out of memory。解决办法有三种:
第一种,降低模型规格,把large换成medium或small。
第二种,开启分块转写,长度超过一定阈值的音频先按时间切分再处理。
第三种,放弃本地推理,使用云端ASR接口。这类接口通常按小时或按次计费,对于一次性处理大量视频的场景反而更划算。
7. 最佳实践与工程建议
7.1 转写质量优先于技术炫技
语音识别是整个知识库的地基。如果转写文本错误百出,后面的向量化和问答质量都无从谈起。在生产项目中,建议:
- 优先使用效果更好的大模型,而不是追求速度。
- 对专业领域内容,维护一个自定义词汇表,在调用ASR时传入。
- 对关键视频做人工抽检,发现系统性错误及时调整参数。
- 保留原始音频和视频文件,方便后续重新转写。
7.2 知识库设计要“分而治之”
不建议把几千个视频全部塞进同一个向量集合。更合理的方式是按主题、按课程、按时间分批构建子知识库,每个知识库对应独立的Collection或命名空间。这样一方面可以控制单库规模,提高检索精度;另一方面可以针对不同子库使用不同的检索参数。例如一套Java教程和一套产品经理分享课程,用户提问的方式和期望的答案粒度完全不同。
另外,在元数据设计上,建议额外记录以下字段:
course_name:课程名称,便于按课程过滤。chapter_title:章节标题,便于展示时直接定位。upload_date:视频发布时间,便于排除过时内容。language:语言,区分中英文内容。
7.3 RAG检索效果的三个优化方向
很多人搭好RAG之后发现检索效果不理想,通常会从三个方向下手。
第一是混合检索。纯向量检索对语义理解好,但对关键词精确匹配不够敏感。可以加入BM25关键词检索,把两种结果做分数融合。这样用户搜索“P99延迟”时,即使语义相近的向量检索没命中,关键词检索也能兜底。
第二是重排序(Rerank)。向量检索先粗筛出20个候选片段,再用交叉编码器模型对候选片段和用户问题做精排,最终只取前5个送入大模型。这一步通常能显著提升回答质量,但会引入额外计算开销。
第三是查询改写。用户提问往往是口语化、不完整的,可以在检索前让LLM把问题改写为更规范的搜索词。例如用户问“那个第三个视频里说的优化方法具体是什么”,可以改写为“第三个视频中提到的优化方法”。
7.4 版权与合规提醒
将B站视频转文字并建立知识库,只能用于个人学习、研究、笔记整理等合理使用场景。如果需要二次发布、商用或大量复制内容,必须先获得原作者授权。下载视频时也要遵守平台规则,控制请求频率,不要对平台造成压力。知识库本身如果涉及他人版权内容,不要在公开渠道随意分享。
7.5 生产环境部署建议
如果要把这套能力做成团队内部服务,建议关注以下几点:
- 使用异步任务队列(如Celery或Arq)来管理视频下载和转写任务,避免阻塞主流程。
- 将视频下载、音频抽取、语音识别、向量化拆分为独立步骤,各自支持失败重试。
- 记录每条任务的处理状态和日志,方便追踪失败原因。
- 为向量数据库和大模型接口设置调用配额,防止资源被单个任务耗尽。
- 如果使用云端API,注意配置好密钥管理,不要把API Key提交到代码仓库。
8. 从视频到知识库,下一步怎么走
通过本文的完整拆解,你已经掌握了从B站多P视频下载、批量语音转写、跨视频知识库构建到RAG问答与角标溯源的核心流程。整个过程可以概括为:视频文件先转成带时间戳的文本,文本再切成语义片段并向量化入库,问答时通过检索增强生成返回带引用来源的答案。
如果你使用的是谛听AI这类产品化工具,可以直接在界面上完成上述流程,无需本地搭建复杂环境;如果你需要定制化功能,也可以参考文中的代码和设计思路,把流程集成到自己的系统中。建议先拿自己收藏夹里的一门课程做实验,跑通之后再逐步扩展视频数量。
下一步值得继续深入的方向包括:接入更强的中文ASR模型以提升转写质量、引入重排序模型优化检索相关性、设计更合理的切片策略以适配不同视频类型、以及将知识库与Obsidian等笔记工具打通,形成“视频学习-笔记整理-知识回顾”的完整闭环。
如果这套方案对你有帮助,可以收藏备用。后续遇到具体问题,欢迎在评论区交流,我会持续整理视频知识库相关的高频踩坑案例与优化方案。