开篇废话
最近蛋疼想把 Pixiv 的动图作品给切换成视频而不是 Canvas 渲染,于是写起了油猴插件。
具体想法是检测到网页在放动图的时候把 canvas
换成 video
,也就是需要检测到网页上已经有 canvas 元素后进行安排。
过程
了解相关知识
这里的 Proxy 并不是大家所熟知的那种代理,而是一个对象的代理,不要被骗了 MDN 多翻一翻,还有补点 ES6 知识大概好点:
确定劫持对象
我的X想法为:只要网页在放动图,则自动把 <canvas>
换成 <video>
所以我的劫持对象为 document.createElement
,当这个函数传入 "canvas"
则抛出异常阻止 <canvas>
生成。
相关代码参考:
document.createElement = new Proxy(document.createElement, {
apply: (target, thisArg, argArray) => {
if (argArray[0] === 'canvas') {
throw 'e';
}
const originProxy = target.apply(thisArg, argArray);
return originProxy;
}
});
然而在实际劫持过程中会因为后续 canvas 绘画相关过程都无法进行,触发了 Pixiv 的异常页面出现,所以是行不通的(也可以安排,但是后续 hook 感觉太多了)。
并且就算行得通,在 Canvas 渲染之前还是会下载动图文件,相当于重复请求了,不太划算。
于是改用劫持请求方式来安排,在 Network 标签中查看请求,发现 Pixiv 是用 fetch 函数来 ajax 的:
这里还是确认到底要 Proxy 什么对象,可能是 XMLHttpRequest 也可能是 fetch ,而 XMLHttpRequest 在本文不再介绍。
于是我们劫持 fetch,本文从这里正式开始,相关代码参考:
window.fetch = new Proxy(window.fetch, {
apply: async (target, thisArg, argArray) => {
// output pathname (maybe)
console.log(argArray[0]);
let originProxy = await target.apply(thisArg, argArray);
return originProxy;
}
});
测试看上去 hook 到了(有输出相关请求链接)
然后我们需要拦截掉动图的相关请求。
我们手动 fetch 一下参考 fetch 函数会返回什么结果(也可以参考 MDN)
(此部分其实可以略,只是了解下而已,因为我们暂时不需要返回真实数据,让 ta 报错就够了)
可以看到返回结果有个 status
的 object,那么我们可以直接返回 {status: 404}
来当请求是 404
参考代码:
window.fetch = new Proxy(window.fetch, {
apply: async (target, thisArg, argArray) => {
const pathname = argArray[0];
if(pathname.includes('ugoira_meta')) {
// 此处添加替换播放器代码
replaceUgoiraPlayer();
// 请求拦截成 404
return {
status: 404
};
// 也可以直接 throw
// throw 'e';
}
let originProxy = await target.apply(thisArg, argArray);
// 此处可以拦截后修改返回值,代码不再演示
return originProxy;
}
});
经过测试,动图的相关请求 404 只是中断渲染:
并不会让整个页面变成错误页面,那么就可以接着安排 <video>
标签进去(此处省略)
于是就劫持完成了。
反劫持
我们让 fetch 不可配置以及不可覆写即可简单防御。
参考代码:
Object.defineProperty(window,'fetch', {
writable:false,
configurable:false
});
效果:
和之前文章套路一样呢
总结
继上次 解锁 ABEMA 限制 后,又学到了一个 Proxy 方法来 hook 对象,还是挺灵活的。
然后可以拿这个添加鉴权 headers,或者代码里面藏毒让脚本小子捉摸不透请求方式来反爬虫(参考 DeepL),亦或者像我一样给一些网站附加点脚本……
总之玩法多多,局限你的只有脑洞!
完