等《开端》更新闲得无聊,写个前端脚本追踪豆瓣评分,没想到这么多坑!

1,238 阅读5分钟

1.前言

放假回家开启了休闲模式,看到腾讯视频上了新的网剧《开端》,啪的一下我就点进去了,一口气追了八集相当过瘾。然后就陷入等更新的无聊和空虚之中。于是想先看看豆瓣的评分和评价,没想到竟然还没出评分。 image.png 莫名地有些在意豆瓣评分(我看好的剧,分数不可能低 :D),有事没事点进网站看看。于是就想着写个简单地脚本定时的查一下评分,评分出来之后通知我。既然是做前端的,就用前端的方式写几行代码,解决这个需求。大概是因为我太菜了,几经尝试,踩了数个坑终于弄出了比较靠谱的方案。

2.初次尝试

分析一下需求:一段代码 --> 《开端》评分除了之后,通过windows通知弹窗通知我。

拆分一下需求,分为以下三步走:

  1. 通过网络请求得到网剧评分
  2. 将得到的网剧评分等信息展示出来
  3. 定时查询展示评分

2.1 查评分

最开始想通过豆瓣网页前端源代码找找查影视剧评分的接口(官方接口当然是首选),翻了半天没找到接口,定睛一看,第一个请求就把这个网页发过来了。好家伙,感情是服务端渲染SSR啊,这就真没办法找到接口了。 image.png 得了,还是上网搜搜接口吧,豆瓣这么大个公司,不能连个接口都不提供吧。

没承想,这还就真没有官方接口,找了半天终于找个一个开源项目提供接口。通过下面的接口调用:

https://movie.querydata.org/api?id=35332289

其中的id是豆瓣id,每个电影的豆瓣网页都可以在网址上看到这个id。https://movie.douban.com/subject/35332289/

至于发网络请求,这本来就是心血来潮的草台班子,用原生js一切从简了,最方便的就是用fetch直接拉数据。 代码如下:

fetch('https://movie.querydata.org/api?id=35332289')
  .then(response => response.json()) //response是fetch的一个对象,通过json方法拿数据
  .then(data => console.log(data));

2.2 发通知

万恶的chorme弹窗,本来是把他关了的,但为了这次的需求,还是暂时打开吧。如何通过web向桌面window发出通知,参考(baipiao)了《H5 notification浏览器桌面通知》。大佬已经将发通知的方法抽象出来了。前人栽树,后人伐木,代码如下:

function notifyMe(title, options) {
  // 先检查浏览器是否支持
  if (!window.Notification) {
    console.log("浏览器不支持通知");
  } else {
    // 检查用户曾经是否同意接受通知
    if (Notification.permission === "granted") {
      var notification = new Notification(title, options); // 显示通知
    } else if (Notification.permission === "default") {
      // 用户还未选择,可以询问用户是否同意发送通知
      Notification.requestPermission().then((permission) => {
        if (permission === "granted") {
          console.log("用户同意授权");
          var notification = new Notification(title, options); // 显示通知
        } else if (permission === "default") {
          console.warn("用户关闭授权 未刷新页面之前 可以再次请求授权");
        } else {
          // denied
          console.log("用户拒绝授权 不能显示通知");
        }
      });
    } else {
      // denied 用户拒绝
      console.log("用户曾经拒绝显示通知");
    }
  }
}

通过以下语句调用:

var options = {
dir: "auto", // 文字方向
body: 豆瓣评分// 通知主体
requireInteraction: true, // 不自动关闭通知
// 通知图标
icon: "https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7b543cfe4fb0428e926dcd42f79bfeb6~tplv-k3u1fbpfcp-watermark.image?",
};

notifyMe("豆瓣评分", options);

2.3 定时器

把这些东西糅合一下,再加上定时器,解决了首次查询先弹一次窗的需求,在加上跨域问题(在他的域中调用,即 movie.querydata.org/api?id=3533… 这个网页的控制台中),毕竟不是官方接口每30秒只能调用一次,定时器间隔得大于30秒。综合以上种种,可得代码如下:

function notifyMe(title, options) {
  // 先检查浏览器是否支持
  if (!window.Notification) {
    console.log("浏览器不支持通知");
  } else {
    // 检查用户曾经是否同意接受通知
    if (Notification.permission === "granted") {
      var notification = new Notification(title, options); // 显示通知
    } else if (Notification.permission === "default") {
      // 用户还未选择,可以询问用户是否同意发送通知
      Notification.requestPermission().then((permission) => {
        if (permission === "granted") {
          console.log("用户同意授权");
          var notification = new Notification(title, options); // 显示通知
        } else if (permission === "default") {
          console.warn("用户关闭授权 未刷新页面之前 可以再次请求授权");
        } else {
          // denied
          console.log("用户拒绝授权 不能显示通知");
        }
      });
    } else {
      // denied 用户拒绝
      console.log("用户曾经拒绝显示通知");
    }
  }
}

function queryMovie(movieId) {
  fetch("https://movie.querydata.org/api?id=" + movieId)
    .then((response) => response.json())
    .then((data) => {
      var options = {
        dir: "auto", // 文字方向
        body: `${data.originalName}:${
          data.doubanRating === "" ? "暂无评分" : data.doubanRating
        }`, // 通知主体
        requireInteraction: true, // 不自动关闭通知
        // 通知图标
        icon: "https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7b543cfe4fb0428e926dcd42f79bfeb6~tplv-k3u1fbpfcp-watermark.image?",
      };
      if (firstQuery || data.doubanRating !== lastStatus) {
        lastStatus = data.doubanRating;
        notifyMe(data.originalName + "豆瓣评分", options);
        if (!firstQuery) {
          clearInterval(LoopId);
        } else {
          firstQuery = !firstQuery;
        }
      } else {
        console.log(
          `评分未改变,仍${
            data.doubanRating === "" ? "无评分" : "为" + data.doubanRating
          }, 查询时间:${Date()}`
        );
      }
    });
}

let lastStatus = "";
let firstQuery = true;

const LoopId = setInterval(() => {
  queryMovie("35332289");
}, 1000 * 40);

image.png

3.二次迭代

万万没想到,免费的接口有使用限制,每天限次数,让我升级账户。 image.png 咱可不能惯着他,另谋它法吧。 久经搜索,仍然没有可以白嫖的接口,怒了。毁灭吧,直接解析html文件了,反正只是拿个评分而已。回到最初的起点, 用fetchresonpse.text()方法拿数据,用正则把内容解析出来。再加上发通知的相同代码:

function notifyMe(title, options) {
  // 先检查浏览器是否支持
  if (!window.Notification) {
    console.log("浏览器不支持通知");
  } else {
    // 检查用户曾经是否同意接受通知
    if (Notification.permission === "granted") {
      var notification = new Notification(title, options); // 显示通知
    } else if (Notification.permission === "default") {
      // 用户还未选择,可以询问用户是否同意发送通知
      Notification.requestPermission().then((permission) => {
        if (permission === "granted") {
          console.log("用户同意授权");
          var notification = new Notification(title, options); // 显示通知
        } else if (permission === "default") {
          console.warn("用户关闭授权 未刷新页面之前 可以再次请求授权");
        } else {
          // denied
          console.log("用户拒绝授权 不能显示通知");
        }
      });
    } else {
      // denied 用户拒绝
      console.log("用户曾经拒绝显示通知");
    }
  }
}

function queryMovie(movieId) {
  fetch("https://movie.douban.com/subject/" + movieId)
    .then((response) => response.text())
    .then((data) => {
      let reg = /<div class="rating_sum">\n(.*)\n(.*)<\/div>/gi;
      let matchs = reg.exec(data);
      if (matchs === null) {
        reg =
          /<strong class="ll rating_num" property="v:average">(.*)<\/strong>/gi;
        matchs = reg.exec(data);
      }
      const doubanRating = matchs[1].trim();
      const regForTitle = /<span property="v:itemreviewed">(.*)<\/span>/g;
      const matchsForTitle = regForTitle.exec(data);
      const title = matchsForTitle[1].trim();

      var options = {
        dir: "auto", // 文字方向
        body: title + " : " + doubanRating, // 通知主体
        requireInteraction: true, // 不自动关闭通知
        // 通知图标
        icon: "https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7b543cfe4fb0428e926dcd42f79bfeb6~tplv-k3u1fbpfcp-watermark.image?",
      };

      if (firstQuery || doubanRating !== lastStatus) {
        lastStatus = doubanRating;
        notifyMe("豆瓣评分", options);
        if (!firstQuery) {
          clearInterval(LoopId);
        } else {
          firstQuery = !firstQuery;
        }
      } else {
        console.log(
          `评分未改变,仍${
            doubanRating === "暂无评分" ? "无评分" : "为" + doubanRating
          }, 查询时间:${Date()}`
        );
      }
    });
}

let lastStatus = "";
let firstQuery = true;

const LoopId = setInterval(() => {
  queryMovie("35332289");
}, 1000 * 40);

然后跨域又寄了,解决办法相同,到他的域中去( movie.douban.com/ ),再在其控制台运行这些代码,一切终于正常了。

image.png

4.总结

  1. 找个接口都这么难,互联网越来越不开放了
  2. 跨域很烦人
  3. 强推《开端》