尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

B站视频转文字与RAG知识库:从收藏夹到可溯源问答

B站视频转文字与RAG知识库:从收藏夹到可溯源问答 B 站视频转文字单独做一次不难难的是处理完之后的“复用”。收藏夹里躺着一百个视频理论上都是学习材料但真要找某个结论在哪一集、哪个时间点翻起来会非常痛苦。视频转文字只是第一步接下来要做的是多 P 批量转写、跨视频统一检索、AI 按片段回答问题并且每个回答都要能溯源到原视频的具体位置也就是角标溯源。这条链路本质上是 RAG 知识库在视频场景下的落地。本文会把从 B 站收藏到可检索知识库的完整过程拆开讲清楚每一步为什么存在、用什么工具、怎么写代码、怎么验证最后落到一套可以长期使用的个人知识库方案上。1. 先把需求拆清楚收藏夹里的视频为什么难以当作知识库来用1.1 B 站收藏夹的真实使用困境收藏夹的问题不是“存不进去”而是“取不出来”。视频是时间轴上的线性信息流用户看到某个知识点时脑子里想的是“我记得某个 UP 主讲过这个概念”但无法通过关键词搜到对应片段。浏览器搜索只能搜到标题和简介搜不到视频里的具体语音内容即使视频本身有字幕也很少有人会把字幕导出、切片、做成索引。把收藏夹变成知识库首先要把“不可检索的音频流”转成“可检索的文本块”。这一步做好之后后面的 AI 问答、角标溯源才有落点。1.2 视频知识库的核心把时间轴转成可检索文本视频内容本质上是一个带时间轴的文本流。语音识别引擎输出的不是一句话而是一条包含开始时间、结束时间、文本内容的段落序列。比如[ {start: 12.5, end: 28.3, text: 向量是线性代数中最基本的概念}, {start: 28.3, end: 45.1, text: 它表示既有大小又有方向的量} ]只要保留时间戳这段文本就可以随时反查回视频。它同时具备两个价值一是可以通过关键词或向量语义检索被找到二是可以回到原视频的对应时间点观看上下文。这就是“转写”和“观看笔记”的本质区别。普通笔记是用户整理后的结论而带时间戳的转写文本是原视频的忠实结构化管理版本后者更适合作为知识库底座。1.3 多 P 批量、跨视频问答、角标溯源分别解决什么问题多 P 批量解决的是规模化问题。B 站一个课程视频可能有几十个分 P手动一个个转写完全不可行必须做成批处理任务。跨视频 AI 问答解决的是检索范围问题。用户的问题往往跨视频跨章节不能只在一个视频里找答案而是要在整个收藏集里召回相关内容再让模型回答。角标溯源解决的是可信度问题。大模型回答如果没有来源就失去了知识库的价值。角标的作用是让每个回答片段都能指回“哪个视频、第几 P、什么时间范围”。这三件事连起来才构成“收藏夹秒变知识库”的含义。2. 视频转文字 RAG 的技术链路和纯文档知识库有什么不同2.1 一条标准链路里的四个环节一条可用的视频知识库链路至少包含四段语音识别转录把视频音频转成带时间戳的文本段落。文本切片把转录段落按合理长度合并成适合向量化的块。向量化索引对文本块生成 embedding连同视频元数据一起写入向量库。检索问答用户问题先向量检索召回相关片段再交给大模型生成带有来源角标的回答。用代码表达这条链路的输入是一个 B 站视频页面 URL输出是一个问答结果 JSON中间全部由批量任务串联。2.2 视频场景和文档 RAG 的关键差异视频转文字的知识库和普通的文档知识库在 RAG 层面最大的区别在于数据单元不同。文档切分后通常只需要保留文字和页码视频切分后必须额外保留三个字段视频 ID、分 P 序号、开始时间戳。少了任何一个字段角标溯源就无法实现。这也是很多人直接用现成 RAG 框架导入字幕文件后效果不好的原因——导入工具只识别了文本内容丢掉了时间索引关系。此外语音识别文本和书面文档差异明显。转写文本没有标点规范口语化严重可能存在错别字、数字识别错误、人名错误。这意味着切片前的清洗比文档场景更重要。2.3 一站式工具与自建流程如何取舍像标题中提到的谛听 AI以及市面上常见的知识库产品目标都是把这条链路产品化。用户不需要写代码上传视频或粘贴收藏夹链接就能得到可检索的视频文本和问答对话。这类工具适合不想折腾环境、追求开箱即用的用户。自建流程则适合需要深度控制的人。好处是可以自由选择转录引擎、调整切片策略、自定义溯源格式并把数据完全掌握在自己手里。代价是环境搭建、依赖维护和后期调优都要自己处理。从学习角度看建议先自建一个最小闭环理解链路是怎么跑的然后再决定是否切换到一站式工具。下面几章就按自建方案逐步展开。3. 环境准备与技术选型3.1 转录引擎选型转录是整个流程中算力消耗最大的一环。常见选择如下工具特点适合场景注意事项faster-whisperWhisper 的 CTranslate2 实现CPU 也能跑本地通用转写中文建议至少用 small 或 medium 模型funasr / paraformer阿里开源中文语音识别模型中文效果好中文长视频批量转写需要 Python 环境模型下载体积较大云端语音识别 API各家云厂商提供对时效和精度要求高的生产环境按时长计费需评估成本B 站官方字幕部分视频有 CC 字幕有字幕的视频可直接使用无时间戳或时间戳粒度不统一如果原始材料没有明确指定转录引擎本地学习环境优先推荐 faster-whisper。它安装简单CPU 上也能跑通输出 segments 天然包含 start 和 end 字段非常贴合后续的溯源需求。3.2 Embedding 与向量数据库选型Embedding 模型负责把文本转成向量。中文环境下常用这几个模型说明使用建议BAAI/bge-small-zh-v1.5轻量本地 CPU 可运行学习环境首选BAAI/bge-m3多语言、多粒度效果更好资源充足时使用云端 embedding API无需本地资源按 token 计费适合生产环境向量库的选择取决于数据量工具类型建议Chroma轻量嵌入式向量库个人学习、几百个视频以内很方便Qdrant独立向量数据库数据规模较大需要过滤和持久化时Milvus分布式向量库生产集群方案运维成本较高PGVectorPostgreSQL 插件业务数据已经存在 Postgres 时3.3 学习环境与生产环境的最小配置差异学习环境可以跑在一台有 8GB 内存的 Mac 或 Linux 机器上用 faster-whisper 的 small 模型加 Chroma 就能完成最小闭环。转录速度会比较慢但足以验证流程。生产环境至少要额外考虑三件事转录任务要队列化避免一次性占满 GPU 或 CPU。视频下载、转录、切片、向量化要分开记录状态任一环节失败可以单独重试。大模型调用要接入稳定的 API 网关超时和限流要有兜底。4. 最小闭环把单个 P 视频转成带时间戳的向量库4.1 准备待处理的音频文件第一件事是从视频页面拿到音频。这里以 yt-dlp 作为示例工具它支持 B 站地址解析。注意下载的视频只用于个人学习场景请勿二次分发或用于侵权用途。yt-dlp -f bestaudio \ -o video_%(id)s.%(ext)s \ --extract-audio --audio-format mp3 \ https://www.bilibili.com/video/BV1xx411c7mD执行完后目录下会出现video_BV1xx411c7mD.mp3文件。如果不想用命令行下载工具也可以直接上传本地录屏或已有视频文件后续转录步骤不变。4.2 转录并输出带时间戳的段落使用 faster-whisper 转录核心是拿到包含 start、end、text 的 segments 列表。from faster_whisper import WhisperModel model WhisperModel(small, devicecpu, compute_typeint8) segments, info model.transcribe( video_BV1xx411c7mD.mp3, vad_filterTrue, languagezh, beam_size5, ) records [] for segment in segments: records.append({ start: round(segment.start, 2), end: round(segment.end, 2), text: segment.text.strip(), }) print(records[:5])这里有几个关键参数需要理解vad_filterTrue会过滤静音段减少无意义转写beam_size5控制解码搜索宽度越大越准但越慢languagezh强制使用中文避免开头几句话被误判为英文。转写完成后建议先把 records 保存为 JSON 文件后续切片和向量化都从 JSON 读取。这样转录失败时不需要重新跑一次语音识别。import json with open(records.json, w, encodingutf-8) as f: json.dump(records, f, ensure_asciiFalse, indent2)4.3 对转录文本切片和向量化转录得到的 records 每段可能只有几十字直接逐段向量化会让语义太碎。需要合并成几百字一个的文本块同时保留块内第一个段落的 start 和最后一个段落的 end。下面这个函数把相邻段落按字符数合并def build_chunks(records, max_chunk_chars500): chunks [] buf [] buf_len 0 def flush(): nonlocal buf, buf_len if not buf: return text .join(item[text] for item in buf).strip() if text: chunks.append({ text: text, start: buf[0][start], end: buf[-1][end], }) buf [] buf_len 0 for record in records: buf.append(record) buf_len len(record[text]) if buf_len max_chunk_chars: flush() flush() return chunks chunks build_chunks(records) print(len(chunks), chunks[0])这段代码的关键是不要用固定长度硬切。语音转写的内容是连续语句切在句中会破坏语义。按段落累积到阈值再成块是相对稳妥的做法。如果想要更精细可以在 flush 时找到最近的句号或问号再切。生成 chunks 后用 embedding 模型向量化并写入 Chromafrom chromadb import Client from chromadb.config import Settings from sentence_transformers import SentenceTransformer embedder SentenceTransformer(BAAI/bge-small-zh-v1.5) client Client(Settings( chroma_db_implduckdbparquet, persist_directory./chroma_data, )) collection client.get_or_create_collection(bilibili_kb) ids [] documents [] metadatas [] embeddings [] for i, chunk in enumerate(chunks): ids.append(fBV1xx411c7mD_p1_{i:04d}) documents.append(chunk[text]) metadatas.append({ bvid: BV1xx411c7mD, title: 线性代数课程, page_index: 1, page_title: P1 向量, start: chunk[start], end: chunk[end], }) embeddings.append(embedder.encode(chunk[text]).tolist()) collection.add( idsids, documentsdocuments, metadatasmetadatas, embeddingsembeddings, )Chroma 的 metadatas 只支持基础类型不要嵌套字典时间戳用 float分 P 序号用 int便于后面按元数据过滤。4.4 保存元数据并验证查询向量库已经写入还需要把视频级元数据单独保存一份包括视频标题、UP 主、分 P 列表、本地音频路径、转写 JSON 路径。建议以 video 为粒度建立目录data/ BV1xx411c7mD/ video.mp3 records.json chunks.json meta.json验证索引是否可用直接查一个问题question 什么是向量 result collection.query( query_embeddings[embedder.encode(question).tolist()], n_results3, where{page_index: 1}, ) for meta in result[metadatas][0]: print(meta[page_title], meta[start], meta[end], meta[title])如果输出里有对应的分P和时间范围说明转录、切片、向量化、索引这条链路已经跑通。5. 多 P 批量处理要建好元数据模型而不是拼文件5.1 多 P 视频为什么不能直接拼文件很多课程视频是几十个分 P。如果直接把所有分 P 的音频拼接成一个文件再转写会带来两个问题一是时间戳从拼接文件开头算起无法映射回每个分 P 的真实位置二是单次转写时间极长中途失败就要全部重来。正确做法是按“视频 BV 号 分 P 序号”作为最小处理单元每个分 P 独立转写独立生成 records最后统一写入同一个向量库靠元数据区分来源。5.2 元数据模型示例一份适合多 P 场景的元数据模型如下{ video: { bvid: BV1xx411c7mD, title: 线性代数 45 讲, uploader: 示例 UP 主, video_url: https://www.bilibili.com/video/BV1xx411c7mD }, pages: [ { page_index: 1, page_title: P1 向量, page_url: https://www.bilibili.com/video/BV1xx411c7mD?p1, status: done }, { page_index: 2, page_title: P2 矩阵, page_url: https://www.bilibili.com/video/BV1xx411c7mD?p2, status: done } ] }每个分 P 转录完成后把状态从 pending 改成 done。这样处理到一半系统重启也能知道哪些分 P 已经完成哪些需要重跑。5.3 批量任务、去重和断点续跑批量处理建议使用任务表而不是 script 里的裸循环。任务表可以用 SQLite也可以直接用 JSON 文件。核心字段是task_id bvid page_index status pending / running / done / failed retry_count error_message处理流程如下解析收藏夹拿到所有视频的 BV 号和分P列表。为每个分P创建 pending 任务。消费者从任务表取 pending 任务置为 running。下载音频转写切片向量化。成功后置为 done失败记录错误并置回 pendingretry_count 加 1。retry_count 超过阈值后任务转人工处理。写入向量库时要注意幂等性。Chroma 的 id 如果已经存在再次 add 通常被视为更新但前一次处理失败时可能留下脏数据。推荐在任务开始时用统一 id 规则删除已有记录再重新写入。collection.delete(where{bvid: BV1xx411c7mD, page_index: 2})删除后用相同 id 规则重新写入可以保证即使任务重复执行也不会出现重复内容。6. 跨视频 AI 问答与角标溯源的实现思路6.1 检索召回先找候选视频片段跨视频问答的第一步不是直接问大模型而是先在向量库中召回多个候选片段。这里有两个关键点不按视频维度过滤除非用户明确指定只看某个视频。召回数量要覆盖上下文建议 n_results 取 5 到 8太少容易漏信息太多会超出模型的上下文窗口。对于语义相近的问题还可以加一次重排。如果没有专门的 rerank 模型最简单的做法是同时用关键词检索和向量检索合并结果后按相关度去重排序。这一步能明显减少模型答非所问的可能。6.2 Prompt 构造让模型只基于给定片段回答召回的片段需要组装成结构化上下文再传给大模型。Prompt 里的每个片段都要带上视频标题、分P编号、时间范围这是后面角标生成的前提。你是一个基于视频收藏集的知识库问答助手。 请只根据下面提供的视频片段回答用户问题。 如果片段中没有足够信息请直接回答“收藏集中没有找到相关内容”。 片段列表 [片段0] 来源线性代数课程 P1时间 12.50 - 28.30 内容向量是线性代数中最基本的概念表示既有大小又有方向的量。 [片段1] 来源线性代数课程 P2时间 300.10 - 320.02 内容矩阵可以看作线性变换的表示形式。 用户问题什么是向量 请给出答案并在每个观点后面标注对应的片段编号。这段 Prompt 的核心理念是“引用约束”。模型被要求输出观点对应的片段编号而不是自由编造来源。6.3 角标生成与回填从片段元数据到可点击来源模型输出中会出现[片段0]这样的标记。后处理阶段需要把它替换成带可跳转链接的角标。B 站网页端常用的跳转参数是?p分Pt秒数。可以按这个规则生成链接def build_source_link(meta): return ( f{meta[title]} fP{meta[page_index]} f{format_time(meta[start])}-{format_time(meta[end])} ) def format_time(seconds): seconds int(seconds) m, s divmod(seconds, 60) h, m divmod(m, 60) if h 0: return f{h}:{m:02d}:{s:02d} return f{m}:{s:02d}实际拼接链接时需要用https://www.bilibili.com/video/{bvid}?p{page_index}t{int(start)}这种 URL。不同客户端对 t 参数的支持度不一致生成后可以先在网页端验证一次。6.4 返回结构设计AI 问答接口的返回结构建议包含 answer 和 sources 两部分。answer 是最终文本sources 是结构化引用列表。这样前端渲染角标、读取原视频、定位片段都可以直接使用结构化数据而不是从文本里正则解析。{ question: 什么是向量, answer: 根据收藏集中的课程内容向量是线性代数中最基本的概念[1]表示既有大小又有方向的量。, sources: [ { index: 1, bvid: BV1xx411c7mD, title: 线性代数课程, page_index: 1, page_title: P1 向量, start: 12.5, end: 28.3, url: https://www.bilibili.com/video/BV1xx411c7mD?p1t12 } ] }前端拿到 sources 后可以把[1]渲染成可点击角标点击跳转到对应视频时间点。这就是完整的角标溯源效果。7. 验证方式转录、检索、溯源都要能定量检查7.1 转录质量检查转录质量直接影响知识库效果。常见检查项打开 records.json随机抽 10 个段落和原视频画面对比。确认时间戳是否连续相邻 segment 的 start 和 end 是否出现倒挂。检查中文术语和人名错误率。如果人名错得太多需要准备自定义词典或换更强的模型。如果使用 faster-whisper可以开启initial_prompt传入视频里高频出现的专业词汇例如课程名、人名、专有名词能显著降低错字率。7.2 检索质量检查检索质量不能只看主观感受建议准备一组测试问题每道题记录“预期命中的视频片段”和“实际召回结果”。常用的指标有三个指标含义使用场景RecallK前 K 个结果是否包含目标片段判断召回是否漏掉关键信息MRR目标片段在结果中的排名倒数均值判断目标是否排得足够靠前命中时间差召回片段的 start 和目标知识点的实际时间差判断时间戳切分是否合理如果经常漏召回优先检查 embedding 模型是否适合中文、切片是否把相关上下文拆散、是否需要加 rerank。7.3 角标溯源正确性检查溯源正确性检查的核心是模型回答里的每句话是否真的来自它标注的片段来源。检查方法比较简单。把 answer 按照[角标]切分随机抽查每个观点对应的 source 文本确认观点确实能从该 source 中找到依据。如果模型明明引用片段 0但观点在片段 0 中不存在就是“无据引用”需要调整 Prompt 或降低模型自由度。一个有效的约束方式是在 Prompt 中显式写如果观点来自多个片段请并列标注。如果某个片段的文字不足以支持回答请直接说没有相关内容不要补全。8. 常见问题与排查路径8.1 一张排查表覆盖高频故障问题现象常见原因检查方式处理建议转写结果全是英文语言参数未设置或音频开头语音模糊查看 transcripts 的 language 字段强制指定 languagezh时间戳混乱出现倒挂转录模型在长音频下输出不稳定检查 adjacent segment 时间分段转写或增加 vad_filter向量库查询结果不相关embedding 模型不适合中文或切片太碎用固定问题测试不同切片大小换 bge 系列模型调大切片多 P 处理到一半中断没有任务状态记录查看任务表 status实现断点续跑和失败重试AI 回答缺少来源Prompt 未约束角标输出查看模型完整返回在 Prompt 中要求必须标注片段编号角标跳转时间不准t 参数单位计算错误对比页面实际跳转时间确认 start 单位是秒而不是毫秒Chroma 写入重复数据任务重复执行未清理旧记录查看 collection 中的 id写入前按 bvidpage_index 删除8.2 一个典型排查案例假设用户问“第 3 章讲了什么”返回结果却全部来自第 1 章。按顺序排查确认问题本身没有包含第 1 章的措辞。检查向量库中第 3 章的数据是否存在。可以用collection.get(where{page_index: 3})查看。如果第 3 章的文本存在但没被召回说明检索相关度不足。可以人工搜索“第3章”中的关键词看是否能召回对应片段。如果关键词搜索能召回而向量搜索不行说明 embedding 对课程内部术语的区分度不够。此时最有效的办法不是换模型而是给每个片段补充视频标题、UP 主、课程名称作为元数据检索后按元数据过滤或重排。这个案例说明检索问题的排查链路永远是数据在不在 - 能不能搜到 - 排序对不对 - 模型回答是否忠实。跳步排查会浪费大量时间。9. 最佳实践从能跑到长期稳定9.1 把转录文本当作一等公民来治理转录文本不是一次性中间产物而是知识库的原始资产。建议做到三条转录完成后不删除 records.json后续切片策略调整时可以直接复用。对转录文本做简单清洗替换常见错字、去掉重复语气词。保存原始音频路径方便后续重新转录验证。9.2 切分尺寸和重叠设计切块大小没有绝对标准建议按这个原则调视频口语一行的信息密度低于书面文档500 到 800 字一个 chunk 是可接受区间。如果问题是短问答型块小一点保证召回内容聚焦。如果问题是综述型块大一点让模型有足够上下文。增加 60 到 100 字的重叠可以减少边界切断造成的语义断裂。9.3 建立长期可用的检查清单每次新增一批视频到知识库前走一遍下面的清单检查项确认方式视频是否已有授权或属于个人学习范围人工确认音频文件是否完整检查文件时长与页面时长转录是否用了正确语言参数查看 info.language时间戳是否连续脚本检查倒挂每个分 P 的元数据是否完整检查 bvid、page_index、page_title向量库写入是否幂等任务重复执行不产生重复 chunk问答是否返回角标随机问一句验证角标跳转链接是否有效点击验证9.4 生产环境额外保障如果要把这套方案发布成服务或部署在团队环境还需要补充转录任务接入消息队列避免长任务阻塞 Web 服务。配置中心化管理大模型 API Key 不落在代码里。监控转录失败率、检索耗时、大模型响应时间。定期备份向量库和 records.json。大模型接口超时后要有降级逻辑比如返回“检索成功但模型服务不可用”。9.5 扩展方向这套架构可以自然延伸出几个方向接入弹幕和评论文本作为视频内容的补充知识源。增加演讲人识别按说话人切分知识块。对接完整 RAG 框架比如 Dify、RAGFlow、MaxKB把检索、对话、权限管理交给现成平台。把视频转文字结果导出为 Markdown 或 Obsidian 笔记形成个人笔记体系。无论扩展到哪里核心判断都不变视频知识库的可用性取决于转录质量、元数据完整度和溯源能力而不是模型参数越大越好。先把单视频转写和最小 RAG 闭环跑通再考虑批量、评估和上层产品化是一条更稳的落地路径。对新手来说最有价值的练习不是追求一键脚本而是亲手把转录、切片、向量化、答案回填这段链路写一遍因为所有后续优化最终都要回到这一段管线里找答案。
返回列表