分析自己在大学期间读过的书(四)

前情提要

前一篇文章中,因为豆瓣的 API 请求限制,我无法一次性请求整个读书记录的信息列表,于是想到在每个请求前随机等待若干时间,以避免豆瓣的请求限制。

前有堵截,后有追兵

文档描述与实际情况不一致?

运行代码,发现还是无法获得预期效果。用 Postman 再次查看信息,发现出现的错误信息跟之前一样:

{
    "msg": "rate_limit_exceeded2: 43.243.12.21",
    "code": 112,
    "request": "GET /v2/book/search"
}

又到网络上搜索资料,这次发现,有很多开发者遇到了类似的问题,而且他们在自己的博客中指出,豆瓣限制的其实不只是文档中写明的一小时的请求频率,还有一分钟的请求频率。

我将信将疑,等时间限制过了之后,用 Postman 再次发送请求,这次我重点查看 response header,发现有了两个参数跟文档上描述得不一致。

插图

分析自己在大学期间读过的书(四)_第1张图片
我的请求限制是 100 次
分析自己在大学期间读过的书(四)_第2张图片
豆瓣文档明明写的是 500 次

插图

文档上最低的请求限制是 500 次每小时,可在我得到的响应中,写得却是 100 次每小时。
难道是因为我没有申请豆瓣的开发者账号吗?
算了,且不纠结这个。

每分钟 35 次限制?

看别人的博客时,发现有的博主说到,豆瓣的 API 还有个每分钟不能超过 35 个请求的限制。
我不是很请求这个限制是否真的有,也不想试,因为请求次数很宝贵,试上两次就得等个一小时才能重新发请求了。
干脆直接就将这个条件纳入考虑。

每分钟速度限制 + 每小时次数限制

综合考虑到这两个限制之后,我大概的思路是这个样子的:

  1. 将两百多条记录做个 partition,分为四份,一个小时请求一次
  2. 每个请求前,随机等待 0 - 5 秒钟 (试了十几次,最长的一次请求返回之间大概是三秒,稍微多等待一点时间)

程序是下面这个样子

const agent = require('superagent');
const async = require('async');
const _ = require('underscore');
const bookTitleList = require('./book_title_list');

function sleep(milliseconds) {
  let start = new Date().getTime();
  for (let i = 0; i < 1e7; i++) {
    if ((new Date().getTime() - start) > milliseconds) {
      break;
    }
  }
}

function random_sleep(second) {
  sleep(Math.floor((Math.random() * second) + 1) * 1000)
}

function requestTags(bookTitle, done) {
  random_sleep(5);
  agent.get(encodeURI(`https://api.douban.com/v2/book/search?q="${bookTitle}"&count=1`))
       .end((err, res) => {
           if (err) {
             console.log(err);
           } else {
             const tag = res.body.books[0].tags;
             done(null, tag);
           }
         }
       );
}


function partition(items, size) {
  let result = _.groupBy(items, function(item, i) {
    return Math.floor(i/size);
  });
  return _.values(result);
}

const milliseconds_of_one_hour = 60 * 60 * 1000;

partition(bookTitleList, 80).forEach(subList => {
  async.map(subList, requestTags, (err, tags) => {
    tags.forEach(tag => {
      console.log(tag);
    })
  });
  sleep(milliseconds_of_one_hour);
})

写好程序之后,我满心欢喜,运行命令,就直接工作去了。想着晚上下班了直接看效果就行了

当头棒喝

没想到呀没想到,结果还是错的,这是怎么搞得!
命名已经将时间限制和次数限制都加上了呀,还有什么其他问题呢?
仔细地看了错误信息,发现很奇怪的一点,有几个请求是成功的,但是还是无法拿到 tag,报了空指针异常,也就是在 const tag = res.body.books[0].tags; 这一行报错了。
欸,难道是这本书不存在。

的确有可能,为了验证这个想法,我加了个防御语句

let book = res.body.books[0];
const tag = book ? book.tags : `=======================================================empty book ${bookTitle}`

只有当响应中包含 books 并且第一个元素不为空时,我才去取 tags,对于不存在的书,返回特定的字符串加上书名,后面好看得出来是哪本书没有得到响应。

Deadline 是第一生产力

改完之后,发现时间已经不够用了,距离 11 点写作训练的截止时间只剩下不到两小时。如果还按照这个程序执行的话,我就无法完成今天的训练了。

情急之下,我把读书记录分为 8 份,每份 30 条记录左右。手动执行程序,在一个小时多一点的时间内,拿到了所有的豆瓣标签,并将它们合到了一块。

重新使用抄来的 python 程序,成功生成词云图,由于这次直接使用标签而不是书名,所以我拿掉了 分词 的语句。但是,又遇到了编码的问题,几经周
折,最后终于在既定时间内以非常丑陋的代码既定的目标。

最后生成的标签词云如下:

分析自己在大学期间读过的书(四)_第3张图片
大学读书记录 豆瓣标签

对比上一次使用分词工具对书名处理过后得到的词云图,结果看起来区别还是很大的。


分析自己在大学期间读过的书(四)_第4张图片
book history

比如,在新的图片中,看不到重复的词(比如旧图中重复的 Java),也没有被错误分开的词语(比如 皮格马利翁效应 )。总的来说,这次生成的词云应该算是成功的。

未完待续……

因为赶时间,所以我最后是以非常丑陋的代码实现的这些功能。在下一篇文章给中,我想讲讲我如何重构我这些丑陋的代码,使得它们不那么丑陋(挽回一点面子 -_-|| )。

如果你想看下有多丑陋,点击前往代码库

你可能感兴趣的:(分析自己在大学期间读过的书(四))