月度归档: 2022年8月

无刷新页面监听URL变化实现

上一篇文章中,我在LeetCode抓取题解的油猴脚本中新增了一个功能,在题解页面,单独显示一个按钮,点击按钮之后可以抓取当前页面的题解内容。

到这里就产生了一个问题,如何判断当前页面是一个题解的页面呢,举一个简单的题解页面的URL的例子

https://leetcode.cn/problems/find-duplicate-subtrees/solution/by-cheungq-6-fkom/

我们可以看到,除去前面的“https://”协议、“leetcode.cn”域名,后面的“/problems/find-duplicate-subtrees/solution/by-cheungq-6-fkom/”部分就是我们需要解析的部分,这部分内容存在于“window.location.pathname”之中,我们可以随时很方便的获取到,只需判断下是不是

/problems/{Question_Slug}/solution/{Solution_Slug}/

这样的格式即可,当然如果你非常熟悉正则的方式,也可以用正则匹配判断。这样我们就可以知道这个页面是不是一个题解的页面了,如果当前页面是一个题解页面,则我们进行控制显示一个抓取当前题解的按钮

在解决了题解页面判断之后,重新回到LeetCode网站看下,这时会发现我们遇到了新的问题。LeetCode的题目和题解页面使用的是无刷新的方法来渲染页面的,点击题目或者题解内容,页面进行渲染,同时Url进行变更,页面不进行刷新操作。这样的方式大大提升了用户的浏览体验。而很明显我们可以看到使用的不是hash模式的URL,而是history模式的实现。

hash模式的URL是在URL结尾拼接上“#”符及相关参数来实现的,变更URL中“#”后面的内容不会引发页面刷新操作,“#”原本是作为页面锚点的功能存在的

熟悉前端的小伙伴应该对hash模式history模式这两个概念非常了解了,基本属于面试必问基础题系列,如果不知道的可以再自行百度一下

那么在有了以上的基础认知之后,我们就开始着手对history模式URL变更进行监听操作

不同于hash模式的URL,当hash模式的URL发生变更的时候,会触发window.onhashchange事件(参见Window:hashchange event),这样的话我们只要监听window.onhashchange事件就可以了知道URL变更了。而history模式下,是通过history对象来操作实现的,根据MDN文档上的说明信息,在history操作的时候会触发popstate事件,不过下面有一条额外的备注

https://developer.mozilla.org/zh-CN/docs/Web/API/Window/popstate_event

调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件

在力扣的网页上试了下window.addEventListener('popstate',function)相关的操作,确实在点击了之后没有反应。

那么到这里我们又有了另一个思路,参照之前的【油猴脚本】关于用油猴脚本爬取考试题库这件事文章中使用的手法,我们自己建个replaceState和pushState方法替换掉原来history中的replaceState和pushState方法,在我们自己建的方法中调用原来浏览器的对应方法,并向外抛出一个相应的事件,于是就有了如下代码

let _historyWrap = function(type) {
    const orig = history[type];
    return function() {
        const rv = orig.apply(this, arguments);
        publishCustomEvent(type,arguments)
        return rv;
    };
};
history.pushState = _historyWrap('pushState');
history.replaceState = _historyWrap('replaceState');

其中的publishCustomEvent方法是调用的之前的window.CustomEvent浏览器自定义事件文章中写的自定义浏览器事件相关的代码。

这样,我们只要再监听下这里抛出的pushState和replaceState事件就可以了,代码如下

    window.addEventListener('pushState',(e)=>{
        //do something
        //判断当前url是否是题解页面
    },true)

油猴脚本,抓取LeetCode题解[3]

再更新一下,接之前的版本油猴脚本,抓取LeetCode题解[2],在原来的基础上增加了单独抓取单篇题解的功能,再做这个的时候其实遇到个问题,主要难点在于如何监听浏览器地址栏的变化,另开一个文章来说《无刷新页面监听URL变化实现

至于新增这个功能的原因如下,在使用初版的功能全量的抓取了自己的题解文章之后,对于每天新增的题解不能单独抓取补充到indexDB之中,只能再次全量抓取一下,显然不太方便,所以又改进了下增加了这样的功能

另外我试了下,这个抓取用户题解文章列表的接口是不做权限验证的,也就是说A用户可以抓取B用户的所有题解列表,我找了下使用场景,当A用户点开某个B用户的LeetCode个人主页的时候,下面有一块展示区域是防止当前B用户的题解列表的。这是一个正常使用的场景,也就是说,你可以抓取任何你喜欢的用户的所有题解列表

另外修改了一点样式。在右边单独建了小的Panel把按钮都放进去,到处分散的不大好看,

下一步内容,解析题目、题解中的图片标签,抓取图片内容并base64之后作为文本保存下来,使题解内容脱离对力扣图片资源的依赖

Continue reading

油猴脚本,抓取LeetCode题解[2]

接上篇,油猴脚本,抓取LeetCode题解,补充改版了下,顺便为啥要写这个呢?原因其实很简单,因为之前写了很多题解,都是直接在LeetCode上写的,加起来有快400篇了吧,然后自己这边博客每篇都要搬运一下,比较麻烦费事,所以才想着写了这么个工具。

这次改进做了如下

  1. 引入了IndexDBWrapper(https://www.npmjs.com/package/indexdbwrapper/v/1.0.4),封装了IndexDB的操作过程
  2. 直接任何力扣页面都可以操作了,账户需要登录
  3. 直接抓取我的题解了列表,之后根据列表再抓取题目和自己写的题解
  4. 使用了CustomEvent,抓取了列表之后,发起一个事件,页面同时监听事件,得到列表后进行抓取详细内容
  5. 定义了MY_ACCOUNT,需要手动替换为自己的账号ID
  6. 完成后点击下载按钮生成一个json文件下载
  7. 代码中对返回结果内容的字段进行了一些删除,删掉不需要的字段信息,缩小下载文件的大小

之后要做的事情,既然之前自己的写的题解已经拿到了,那么就是想办法再展示出来了,下一步可能想把下载得到的文件通过一个前端项目展示出来,不过这里还是有点问题,部分题解中含有图片内容,这个内容还是存在LeetCode服务器上的,这个还需要另外再想办法获取下来

Continue reading