前情提要
前一篇文章中,因為豆瓣的 API 請求限制产喉,我無法一次性請求整個讀書記錄的信息列表敢会,于是想到在每個請求前隨機等待若干時間,以避免豆瓣的請求限制鸥昏。
前有堵截,后有追兵
文檔描述與實際情況不一致障涯?
運行代碼膳汪,發(fā)現(xiàn)還是無法獲得預期效果。用 Postman
再次查看信息粘我,發(fā)現(xiàn)出現(xiàn)的錯誤信息跟之前一樣:
{
"msg": "rate_limit_exceeded2: 43.243.12.21",
"code": 112,
"request": "GET /v2/book/search"
}
又到網(wǎng)絡(luò)上搜索資料媳谁,這次發(fā)現(xiàn)友酱,有很多開發(fā)者遇到了類似的問題柔纵,而且他們在自己的博客中指出,豆瓣限制的其實不只是文檔中寫明的一小時的請求頻率或详,還有一分鐘的請求頻率郭计。
我將信將疑,等時間限制過了之后昭伸,用 Postman
再次發(fā)送請求,這次我重點查看 response header选调,發(fā)現(xiàn)有了兩個參數(shù)跟文檔上描述得不一致灵份。
插圖
插圖
文檔上最低的請求限制是 500 次每小時填渠,可在我得到的響應中,寫得卻是 100 次每小時氛什。
難道是因為我沒有申請豆瓣的開發(fā)者賬號嗎?
算了徙融,且不糾結(jié)這個瑰谜。
每分鐘 35 次限制树绩?
看別人的博客時,發(fā)現(xiàn)有的博主說到渤早,豆瓣的 API 還有個每分鐘不能超過 35 個請求的限制瘫俊。
我不是很請求這個限制是否真的有悴灵,也不想試骂蓖,因為請求次數(shù)很寶貴,試上兩次就得等個一小時才能重新發(fā)請求了登下。
干脆直接就將這個條件納入考慮被芳。
每分鐘速度限制 + 每小時次數(shù)限制
綜合考慮到這兩個限制之后,我大概的思路是這個樣子的:
- 將兩百多條記錄做個 partition畔濒,分為四份,一個小時請求一次
- 每個請求前赞弥,隨機等待 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);
})
寫好程序之后妇菱,我滿心歡喜暴区,運行命令,就直接工作去了房交。想著晚上下班了直接看效果就行了
當頭棒喝
沒想到呀?jīng)]想到伐割,結(jié)果還是錯的,這是怎么搞得白群!
命名已經(jīng)將時間限制和次數(shù)限制都加上了呀硬霍,還有什么其他問題呢?
仔細地看了錯誤信息,發(fā)現(xiàn)很奇怪的一點躬柬,有幾個請求是成功的抽减,但是還是無法拿到 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 是第一生產(chǎn)力
改完之后肃弟,發(fā)現(xiàn)時間已經(jīng)不夠用了零蓉,距離 11 點寫作訓練的截止時間只剩下不到兩小時。如果還按照這個程序執(zhí)行的話敌蜂,我就無法完成今天的訓練了。
情急之下,我把讀書記錄分為 8 份,每份 30 條記錄左右芳绩。手動執(zhí)行程序撞反,在一個小時多一點的時間內(nèi),拿到了所有的豆瓣標簽嘹害,并將它們合到了一塊吮便。
重新使用抄來的 python
程序,成功生成詞云圖髓需,由于這次直接使用標簽而不是書名僚匆,所以我拿掉了 分詞
的語句。但是咧擂,又遇到了編碼的問題,幾經(jīng)周
折云芦,最后終于在既定時間內(nèi)以非常丑陋的代碼既定的目標攻臀。
最后生成的標簽詞云如下:
對比上一次使用分詞工具對書名處理過后得到的詞云圖刨啸,結(jié)果看起來區(qū)別還是很大的。
比如善已,在新的圖片中离例,看不到重復的詞(比如舊圖中重復的 Java),也沒有被錯誤分開的詞語(比如 皮格馬利翁效應
)宫蛆〉拿停總的來說想虎,這次生成的詞云應該算是成功的。
未完待續(xù)……
因為趕時間岂却,所以我最后是以非常丑陋的代碼實現(xiàn)的這些功能裙椭。在下一篇文章給中,我想講講我如何重構(gòu)我這些丑陋的代碼扫尺,使得它們不那么丑陋(挽回一點面子 -_-|| )你雌。
如果你想看下有多丑陋,點擊前往代碼庫