【油猴脚本】我给B站搜索加上了日期过滤

5,901 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

又是一天闲暇时光,让我来看看B站有什么不错的新视频。

在键盘上缓缓敲出关键词,给enter一击,页面跳转。那么问题来了,从我敲完enter到页面渲染完成,中间发生了什么?嘿嘿,开个玩笑,这不是今天的重点,重点是搜索出来的视频大多数我都已经看过了。要是按照“最新发布”排序,视频质量又不高。这可太扫兴了。

苦恼.jpg

这怎么可能忍得了!必须办它!

开始整活之旅,给B站加上日期过滤。

为什么是用户脚本?

Userscript:通常用JavaScript编写,可用于修改网页代码以增强用户的浏览体验。具体用途有添加网页的快捷按钮,控制视频播放速度以及网站添加其他功能。在Firefox浏览器等桌面浏览器上,Userscript是通过浏览器扩展中的脚本管理器启用的。

浏览器扩展:是扩展网页浏览器功能的插件。一些扩展是使用诸如HTML、JavaScript和CSS之类的网络技术来编写。有些则是使用网页浏览器提供的机器代码和应用程序接口开发。扩展可以改变网页浏览器的使用接口,而不会直接影响网页的可视内容,如增加工具栏。

区别:

  1. API:

    • Userscript实际上就是注入到网页的JavaScript脚本,跟正常的JavaScript开发没什么太大区别,但限制也跟JavaScript差不多,JavaScript做不到的事情,Userscript也基本没戏。例如获取浏览器标签页这种涉及到浏览器本身的操作。
    • 浏览器扩展可以使用浏览器提供的扩展API,实现对浏览器的功能定制,不局限于网页内容。例如Userscript获取不到浏览器标签页,而浏览器扩展可以通过chrome.tabs跟标签页系统进行交互。
  2. 打包:

    • 目前为止,没有特别简单好用的打包工具对Userscript进行支持,无法保证打包后脚本描述注释在最顶部。
    • 相比之下,浏览器扩展的工程化就要好上不少。github也有不少模板。例如vitesse-webext,就是用vite打包的。
  3. 发布:

    • Userscript可以发布在任何静态托管服务(一般是Greasy Fork),用户可以在脚本管理器搜索直接下载。
    • 浏览器插件基本上都需要经过浏览器商店审核,不管是更新还是上架,无法保证实时更新的。

    两者都可以自动更新。

  4. 开发:

    如果只是对网页内容做出修改,优先考虑使用Userscript。如果涉及到浏览器的相关功能,或者需要借助浏览器提供的API实现更丰富的功能,则选择扩展。而Userscript本质上就是扩展的content script。

  5. 性能:

    扩展会在浏览器后台开启一个线程,并一直存在;而Userscript只会在匹配到的网站运行。

实现思路

  1. 给搜索页面加入日期筛选按钮组
  2. 监听按钮点击
  3. 刷新页面重新请求
  4. 拦截请求,重新创建请求获取搜索数据
  5. 过滤视频数组列表,模拟请求结果返回给拦截的请求

具体实现

  1. 为什么要刷新页面重新请求

因为日期筛选按钮本身不在B站的开发中,没有绑定请求事件,无法让页面重新渲染。否则就要针对每个分页的视频日期文本判断是否在日期范围内,进行显示隐藏,但是这样不太友好,要玩就要玩大的。

  1. 怎么刷新页面

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);
}
  1. 拦截请求

查看源码可以知道,搜索相关的请求是用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。你的认可,是我不断进步的动力。

当然这只是我的一次小尝试,还有许多需要改进的地方。

比如时间范围比较小时,需要请求比较多次,来满足一页的展示量,造成请求时间比较长。

实现效果

媒体1___.gif

视频是裁剪掉了骨架屏的部分,还进行了加速。

具体效果可以下载Bilibili Search Filter By Time体验体验。

注意

  1. 请求搜索时,注意间隔,防止被判为爬虫,被封ip

1540968466430742.gif

  1. B站封装的fetch请求默认是10秒超时
  2. 只对视频做了过滤,请切至视频分栏进行使用,或者fork 源码进行二次开发

正事

images.jpg

本人两年前端开发经验,熟悉vue相关技术栈,工作大多数为h5,经常与客户端打交道。

目前正在深圳寻找更好的工作机会,如果各位老哥的公司里有坑位,麻烦踢踢我,带带老弟。