携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情。
又是一天闲暇时光,让我来看看B站有什么不错的新视频。
在键盘上缓缓敲出关键词,给enter一击,页面跳转。那么问题来了,从我敲完enter到页面渲染完成,中间发生了什么?嘿嘿,开个玩笑,这不是今天的重点,重点是搜索出来的视频大多数我都已经看过了。要是按照“最新发布”排序,视频质量又不高。这可太扫兴了。
这怎么可能忍得了!必须办它!
开始整活之旅,给B站加上日期过滤。
为什么是用户脚本?
Userscript:通常用JavaScript编写,可用于修改网页代码以增强用户的浏览体验。具体用途有添加网页的快捷按钮,控制视频播放速度以及网站添加其他功能。在Firefox浏览器等桌面浏览器上,Userscript是通过浏览器扩展中的脚本管理器启用的。
浏览器扩展:是扩展网页浏览器功能的插件。一些扩展是使用诸如HTML、JavaScript和CSS之类的网络技术来编写。有些则是使用网页浏览器提供的机器代码和应用程序接口开发。扩展可以改变网页浏览器的使用接口,而不会直接影响网页的可视内容,如增加工具栏。
区别:
-
API:
- Userscript实际上就是注入到网页的JavaScript脚本,跟正常的JavaScript开发没什么太大区别,但限制也跟JavaScript差不多,JavaScript做不到的事情,Userscript也基本没戏。例如获取浏览器标签页这种涉及到浏览器本身的操作。
- 浏览器扩展可以使用浏览器提供的扩展API,实现对浏览器的功能定制,不局限于网页内容。例如Userscript获取不到浏览器标签页,而浏览器扩展可以通过
chrome.tabs
跟标签页系统进行交互。
-
打包:
- 目前为止,没有特别简单好用的打包工具对Userscript进行支持,无法保证打包后脚本描述注释在最顶部。
- 相比之下,浏览器扩展的工程化就要好上不少。github也有不少模板。例如vitesse-webext,就是用vite打包的。
-
发布:
- Userscript可以发布在任何静态托管服务(一般是Greasy Fork),用户可以在脚本管理器搜索直接下载。
- 浏览器插件基本上都需要经过浏览器商店审核,不管是更新还是上架,无法保证实时更新的。
两者都可以自动更新。
-
开发:
如果只是对网页内容做出修改,优先考虑使用Userscript。如果涉及到浏览器的相关功能,或者需要借助浏览器提供的API实现更丰富的功能,则选择扩展。而Userscript本质上就是扩展的content script。
-
性能:
扩展会在浏览器后台开启一个线程,并一直存在;而Userscript只会在匹配到的网站运行。
实现思路
- 给搜索页面加入日期筛选按钮组
- 监听按钮点击
- 刷新页面重新请求
- 拦截请求,重新创建请求获取搜索数据
- 过滤视频数组列表,模拟请求结果返回给拦截的请求
具体实现
- 为什么要刷新页面重新请求
因为日期筛选按钮本身不在B站的开发中,没有绑定请求事件,无法让页面重新渲染。否则就要针对每个分页的视频日期文本判断是否在日期范围内,进行显示隐藏,但是这样不太友好,要玩就要玩大的。
- 怎么刷新页面
B站搜索页是用vue开发的,所以我拿到了vue-router实例。
document.addEventListener('DOMContentLoaded', function() {
app = document.querySelector('#i_cecream').__vue_app__;
router = app.config.globalProperties.$router;
route = app.config.globalProperties.$route;
})
所以可以使用router.replace
来进行页面跳转。
但是route
不是响应式的,只能重写router.replace
方法,每次给route
手动重新赋值,实现路由的监听。
const routerReplace = router.replace;
router.replace = function(toRoute) {
route = toRoute;
return routerReplace.call(this, toRoute);
}
- 拦截请求
查看源码可以知道,搜索相关的请求是用fetch,所以需要重写fetch。
// 返回json结果
let responseJson = null;
// 过滤的结果
let result = [];
// 日期过滤的页码
let actualPage = 1;
// b站对应页面
let requestPage = 1;
// 数量
let pageSize = 0;
const originFetch = fetch;
unsafeWindow.fetch = async function(url, options) {
// 只针对视频搜索接口
let params = getQueryObject(url);
if(url.indexOf('x/web-interface/search/type') > -1 && params.search_type === 'video' && date !== 'none') {
actualPage = params.page;
pageSize = params.page_size;
if(result.length < actualPage * pageSize) {
await requestData(url, options);
}
let reponseResult = result.slice((actualPage - 1) * pageSize, actualPage * pageSize);
let response = new Response();
response.json = function() {
return new Promise(resolve => {
responseJson.data.page = +actualPage;
responseJson.data.result = reponseResult;
resolve(responseJson);
})
}
return response;
} else {
return originFetch(url, options);
}
}
具体代码可以来我的github查看,也请各位老哥指点指点。
觉得不错的话,可以顺手点个star。你的认可,是我不断进步的动力。
当然这只是我的一次小尝试,还有许多需要改进的地方。
比如时间范围比较小时,需要请求比较多次,来满足一页的展示量,造成请求时间比较长。
实现效果
视频是裁剪掉了骨架屏的部分,还进行了加速。
具体效果可以下载Bilibili Search Filter By Time体验体验。
注意
- 请求搜索时,注意间隔,防止被判为爬虫,被封ip
- B站封装的fetch请求默认是10秒超时
- 只对视频做了过滤,请切至视频分栏进行使用,或者fork 源码进行二次开发
正事
本人两年前端开发经验,熟悉vue相关技术栈,工作大多数为h5,经常与客户端打交道。
目前正在深圳寻找更好的工作机会,如果各位老哥的公司里有坑位,麻烦踢踢我,带带老弟。