使用教程、源码、下载地址都在这里:https://v2think.com/ad-killer
这件事的技术含量不高,但是它很有趣。这是我制作的一款插件,插件的下载地址、源码以及使用方式在这里。如果你是在微信公众号上阅读到的这篇文章,请点击左下角的阅读原文获取。
同时它也开源的,其解决问题的核心思路也是本文想要分享的。希望它能给你带来更多实用 AI 解决问题的灵感。
这是视频演示
以下是图文讲解
简单来说插件的广告识别分为两个步骤,首先如果它发现该视频具备广告识别的条件,在浏览器周围会出现浅蓝色光晕,代表它正在思考

一旦识别完成之后,光晕消失。在进度条上出现广告时间区间

在播放极端即将进入广告区间时,播放器周围会出现五彩光晕进行提示,表示即将跳过该广告区间

简单来说通过字幕。
网站在加载每个视频时都需要请求一个路径为api.bilibili.com/x/player/wbi/v2的接口,我不知道这个接口是做什么的,但是在这个接口中会返回字幕的“元信息”。之所以称之为元信息,是因为它不包含字幕本身,而是包含围绕字幕的有关其他信息,例如该字幕属于哪国语言,该字幕是否由 AI 生成,以及最重要的请求字幕用的 URL
注意,接口只会在登录的情况下才会返回字幕信息。同时注意字幕的 URL 中是带有 token 的,所以你无法一直持有并访问它,因为 token 需要被更新。

我通常会选取中文字幕,并且找到其中的 URL ,然后发送请求,得到的字幕结果内容如下:

理论上我认为把这段 JSON 发给 AI ,它就可以识别出其中的广告。但是为了确保准确性,以及节省 token ,这里我们需要将其传化为更为简单的结构,转化的函数如下:
export function convertSubtitleObjToStr(subtitles: subtitle[]): string {
return subtitles.map((sub: subtitle) => {
const { from, to, content } = sub
const subtitleStr: SubtitleString = `[${from}-${to}]:${content}`;
return subtitleStr;
}).join(';')
}
转化后的结果如下:
[0.82-2.06]:哇来了哇;[2.06-5.48]哦让我们去看看这个今天就吃一桶螺蛳粉了;[5.48-9.41]哇这哇哦终于吃螺蛳粉了;
在将字幕转化为指定格式之后,需要将其发送至 AI ,送使用的提示词( prompt )如下:
接下我会分享给你一段视频字幕,该段字幕由多个字幕语句组成。 每一句字幕包含三部分内容,分别是起始时间,结束时间,以及字幕内容,格式如下:[{起始时间}-{结束时间}]:{字幕内容}。语句之间由分号(;)隔开。 帮助我分析其中哪些是与视频无关的广告内容,给出其中连续广告内容起始时间和终止时间。我可能还会分享给你视频的标题以及视频的描述,用于辅助你判断广告内容 如果存在广告内容,请将广告的起止时间返回给我,返回格式为:{startTime: number, endTime: number} 如果不存在广告内容,返回 null 字幕内容如下:
因为我需要在网页播放器的时间轴上精确的渲染出来广告时间段,所以在上述提示词中我明确告诉 AI 将广告的起止时间以 JSON 的格式返回。
以上能够覆盖 90%的情况了,但是在代码的实现过程中还是有必要解决一下边缘情况。
首先在提示词中指定返回的 JSON 格式并不靠谱,于是我选择在调用 SDK 的代码中以代码的方式指定返回的 JSON schema
const responseSchema = {
type: 'OBJECT',
properties: {
startTime: {type: 'number', nullable: false},
endTime: {type: 'number', nullable: false},
},
required: ['startTime', 'endTime'],
};
const response = await geminiClient.models.generateContent({
model: aiModel,
config: {
responseJsonSchema: responseSchema,
responseMimeType: "application/json",
httpOptions: {
timeout: 1000 * 60,
}
},
contents: ''
});
其次我们还可以通过向 AI 提供更丰富的信息来协助他,例如视频的标题和描述,所以的我最终喂给 AI 的最终提示词中实际上是包含视频的标题以及描述的:
接下我会分享给你一段视频字幕,该段字幕由多个字幕语句组成
……
字幕内容如下: xxxx
视频标题如下: xxxx
视频描述如下: xxx
最后我发现对于短视频 AI 通过字幕判断广告的并不理想,所以默认我不对五分钟以内的视频进行判断。
也许你对实现过程中的一些技术细节感兴趣,这里我把我能够想到的都分享出来。
搜索,这是最简单的方式。
例如在上面的视频中我发现视频字幕中出现了“螺蛳粉”这三个字,于是我就在 Chrome 的开发者工具中全局搜索“螺蛳粉”(你需要再视频一开始加载时就打开开发者工具),结果如下

很明搜索列表的第一个结果就是字幕接口的返回,右边就可以找到该返回所对应的请求 URL 是什么。有了这个 aisubtitle.hdslb.com 关键词之后我就顺藤摸瓜找到返回这个域名的接口

事实上我不用等待页面完全渲染完毕之后从 DOM 中截取,视频的各种基本信息已经被提前被存储到了全局变量window.__INITIAL_STATE__中

通过页面源代码不难看出,该变量早就由后端渲染在了页面上

事实上还有一种较为复杂的场景是播放列表。在这个场景中上一个视频播放完毕之后会自动播放下一个视频。不用担心,此时的window.__INITIAL_STATE__变量内的数据也会得到更新
前端想要发送 API 请求,无非只有两个手段 1 )通过 XMLHttpRequest ; 2 )通过 Fetch API 。经过测试不难发现 Bilibili 网站试用的是前者,于是我选择通过 monkey patch 方式将它“黑”掉,替换成我的实现方法以便我监控每一个发出的请求,以及获取到它们的返回:
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method: string, url: string | URL, ...args: any[]) {
// @ts-ignore
this._url = url.toString();
// @ts-ignore
return originalOpen.call(this, method, url, ...args);
};
XMLHttpRequest.prototype.send = function(...args: any[]) {
// @ts-ignore
const url = this._url;
if (window.location.pathname.startsWith('/video/') && url && url.includes('api.bilibili.com/x/player/wbi/v2')) {
console.log('📺 ✔️ Detected player API request');
因为兼容每一种大模型都需要时间成本,而我的精力实在有限(欢迎给源代码提 pull request )。我唯二非常想集成的大模型是 OpenAI ,苦于他们不接受国内信用卡,亲测 Google Pay 也无效。
集成不同模型最高效的办法是用某个兼容超强的 SDK ,例如 Vercel 提供的AI SDK,又或者某个 Agent 框架例如Mastra,可问题在于虽然多数框架支持的编程语言是 TypeScript ,但框架并支持在浏览器端运行。
事实上我还考虑过使用 Chrome 内置的本地模型——没错,目前 Chrome 支持用户下载一个 Gemini Nano 模型安装在本地(大约 2G 左右),但是该模型判断广告的效果非常差,于是隐藏了该功能。你还可以在我的源码中找到该部分代码
如果每个人都安装了这个插件的话,可想而知同一个热门视频的字幕会被发送给 AI 很多次——这其实是一种浪费。理想情况下,只要我们中的任何一个人拿到了 AI 返回的结果那么它接可以与其他人共同分享。
这才是我最想做的事情:把每个人得到的不同视频的分析结果上传到云端共享,这样一来不仅用户体验可以大大提升,个体花费的时间和金钱也可以节省不少。
1
craftsmanship 8 小时 3 分钟前 via Android
这个可以有👍
|
2
zhouweiluan 7 小时 53 分钟前
牛逼思路
|
3
w3 7 小时 18 分钟前
NB !我的选择是基本不看 B 站。
|
4
HeyWeGo 7 小时 17 分钟前
投放和屏蔽广告的双方互相对抗 ing
|
5
TrackBack 7 小时 16 分钟前
有一个古法众包手动标注的插件:
https://github.com/hanydd/BilibiliSponsorBlock 曾经有人用了 AI 提交标注的广告片段,但是因为太不准被大家踩爆了 不知道 lz 的这个准确的咋样 |
6
panda188 7 小时 14 分钟前 via Android
token 消耗爆表🙃
|
7
listenerri 6 小时 55 分钟前
有趣,感谢分析
|
8
dule 6 小时 31 分钟前
思路不错
|
9
hugsky 6 小时 29 分钟前
https://bsbsb.top/ 空降助手
|
10
dule 6 小时 27 分钟前
@dule 要是有专门的模型能部署本地或者闲置的服务器上不必消耗 token 就更妙了,广告方一般是固定的那些商家,通过维护关键字匹配是不是也可以,只是视频对应的前后进度区间不好获取,这种不明确嵌入式的广告观感真的还不如油管这种开会员能明确跳过的体验感好的,另外 op 还要考虑 b 站会不会律师函警告啥的
|
11
my101du 5 小时 49 分钟前
好像这是违法的。。。
我还想过开发过在网页版播放爱优腾/油管视频的时候,检测开头广告,自动静音,覆盖一个全屏强制背单词/提肛的。 自己用吧,不如花点钱买个会员了。 分享出去吧,要坐穿牢底了。 |
12
zhouqian 5 小时 17 分钟前
这个思路确实可以!
|
14
Cheons 4 小时 59 分钟前 via Android
pilipala 里的 sponsor block 挺好用的。
看了下就是上面说过的 空降助手 |
15
urlk 4 小时 53 分钟前
把功能整合进空降助手, 实现人工+AI 双通道数据采集, 通过共享助手平台数据共享 , 完美
|
16
whoosy 4 小时 50 分钟前
其实还可以结合弹幕来识别
|
17
xu11111111 4 小时 44 分钟前
|
18
tomclancy 4 小时 42 分钟前
最好恐怕是不看了
现在叔叔说没广告实际基本都是广告,也就百大级别接宣发少一些 中小创作者基本没多少创作收入 |
19
ala2008 4 小时 30 分钟前
token 消耗怎么样
|
20
fengkookoo2 3 小时 47 分钟前
只能网页端用
|
21
goodryb 2 小时 47 分钟前
如果做成带服务端,那效果估计会更好,不过隐私这块是个问题
|
22
shuiduoduo 2 小时 33 分钟前 via iPhone
loon 的站插件
|
23
iorilu 1 小时 47 分钟前
蛮厉害的
不过 b 站也没别的法子弄钱, up 主自力更生搞点广告看看也还行把 |
24
shewhen 59 分钟前 via iPhone
样式也肥肠帅气!支持⬆️
|
25
wl9739 32 分钟前
在不远的未来,地球不再缺少算力的时候,这个方案可以铺开来
|