原文地址:http://blog.shenjianshou.cn/?p=170
giithub:https://github.com/TTyb/Baiduindex
最近有很多朋友跟我說,“爬蟲這東西很簡單啊,好像還沒學就已經會了,沒啥深奧的東西哦。看了你之前的教程,不就是一個隊列加一些Http請求嗎,不就是寫寫XPath和正則嗎,你們還做個神箭手雲爬蟲出來?我自己上個廁所就寫完了啊。”
看來是時候拿出我們壓箱底多年的老干媽了,哦不,老干貨了。不嚇屎你們這群小學生我就不在6年級混了。
廢話不多說,所謂爬蟲天坑,敢對得起這個名字的一定不能是等閑之輩。起碼得是過完年老板給你扔這個任務,你兒童節還在頭大的級別。今天第一課,咱們就先找個最難的熱熱身吧:爬取百度指數的關鍵詞搜索指數。
先貼一個logo讓大家跪拜一下
好了,大家平身吧,咱們馬上就正式開始了,想上廁所的趕緊去,不然看完這篇文章估計你就忘了怎么上廁所了。
正式開始之前,先插個廣告:如果土豪朋友不想寫代碼或者中途看不下去的,我們將以下代碼已經打包成一個完整的應用,大家進入神箭手的雲市場搜索百度指數(http://www.shenjianshou.cn/index.php?r=market/product&product_id=500036)就可以看到應用,直接調用既可。
——————————–前方高能預警看也看不完上廁所趕緊去分割線————————————-
咱們正式開始:所謂知己知彼百戰不殆,我們要先了解一下我們的對手。咱們打開百度指數
http://index.baidu.com,映入眼簾的是一個簡單的輸入框。好開心啊,好像不用登錄啊,輸入一個關鍵字試一下吧,輸入神箭手,回車:
果然百度老司機不會讓我們那么開心的。沒事沒事,不就是登錄嗎,也不是沒做過登錄,抓包研究下請求應該不難。我們先找一個賬號登錄看下。登錄之后繼續輸入神箭手:
出來了。哈哈,不難嘛,這不就直接顯示了。然后就按照以前的爬蟲的教程,用XPATH獲取一下數字就可以了,哈哈哈…哈哈..哈……..
慢着,怎么感覺這個數字看着怪怪。嚇得我趕緊掀開被子看看這貨到底是啥:
什么?這是圖!!!!什么?這還是拼圖!!!!什么?這貨居然是異步的拼圖!!!!
怎么樣,感受到天坑的深度沒有?
那咱們就一起來看看怎么見招拆招,用神箭手把百度指數搞定的吧。
開始具體的代碼之前,我們先在神箭手后台新建三個應用,分別是百度指數API,百度登錄爬蟲,百度指數圖片識別AI。
第一章 登錄應用
第一節:咱先搞定登錄
模擬登錄一直是爬蟲的一個老大難問題,雖然我們神箭手提供了智能登錄接口login函數,但是遇上復雜一些的登錄依然無能為力。當然你可以登錄后復制本機Cookie直接用,但這種雕蟲小技百度想封你真得比捏死一只螞蟻還簡單。咱們要有不怕苦,迎難而上的精神,死磕登錄!算了~還是先去搜一下有沒有別人寫過。不搜不知道,一搜嚇一跳啊。咱就隨便找個源碼借鑒借鑒。喬布斯老人家說過嘛,greate artist steal。
https://github.com/qiyeboy/baidulogin/blob/master/baidulogin.py
這個不錯,邏輯清晰,代碼干凈,萬能的github果然不辜負我的重望。我們steal到神箭手平台上來。
首先我們理清這個流程,根據這個代碼我們知道百度的登錄流程是這樣的:
1.通過請求百度首頁或者任意一個百度url獲得百度的基礎cookie。
2.請求 https://passport.baidu.com/v2/api/?getapi 獲得token
3.通過 https://passport.baidu.com/v2/getpublickey 獲得密碼加密的key
4.通過 https://passport.baidu.com/v2/api/?login 將之前獲得到的token,生成的gid,生成的時間戳,用key加密密碼,來提交登錄。
5.如果返回有驗證碼,獲取codestring並請求 https://passport.baidu.com/cgi-bin/genimage 獲得驗證碼圖片並識別。
6.通過 https://passport.baidu.com/v2/?checkvcode 來驗證是否識別成功
7.如果不成功通過 https://passport.baidu.com/v2/?reggetcodestr 來切換驗證碼,在重復前兩步。
8.再次提交 https://passport.baidu.com/v2/api/?login 看是否登錄成功。
好了,這中間很麻煩的兩個地方是
-
驗證碼識別 這個神箭手提供了驗證碼識別的函數,調用方式如下:
var codeUrl = "https://passport.baidu.com/cgi-bin/genimage?" + codeString;
var codeReg = getCaptcha(71, codeUrl);
var imgCaptchaData = JSON.parse(codeReg);
if (imgCaptchaData && imgCaptchaData.ret > 0) {
var result = imgCaptchaData.result;
verifycode = encodeURI(result,"UTF-8");
tt = (new Date()).getTime();
var codeCheck = site.requestUrl("https://passport.baidu.com/v2/?checkvcode&token=" + token + "&tpl=mn&apiver=v3&tt=" + tt + "&verifycode=" + verifycode + "&codestring=" + codeString + "&callback=");
var checkInfo = JSON.parse(codeCheck);
if (checkInfo.errInfo.no != "0") {
console.log("驗證碼識別錯誤");
}
continue;
} else {
console.log("驗證碼識別失敗");
continue;
} -
RSA加密
當我們獲取到key之后需要對密碼進行RSA加密,百度是采用的JS的開源RSA加密庫,神箭手也提供了RSAEncode的方法,具體代碼如下:
var pubkeyJson = site.requestUrl("https://passport.baidu.com/v2/getpublickey?token=" + token + "&tpl=mn&apiver=v3&tt=" + tt + "&gid=" + gid + "&callback=");
var pubkeyInfo = JSON.parse(pubkeyJson.replace(/'/g, """));
var pubkey = pubkeyInfo.pubkey;
var rsakey = pubkeyInfo.key;
var crypttype = "";
var rsaPassword="";
if (rsakey != "") {
crypttype = "12";
//加密密碼
pubkey = pubkey.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "").trim();
rsaPassword = (RSAEncode(password, pubkey));
}
其他的都是一些基礎的請求,大家可以參考github中的代碼進行編寫。
第二節:瘋狂登錄
完成了第一節的工作之后,你以為登錄就沒問題了嗎?你以為你可以用一個帳號爬到天荒地老嗎?有人說限制爬取頻率,這當然是一個方法,但卻不是最好的解決方案。畢竟縮手縮腳,感覺很受限。最好的方案當然是登錄一堆帳號,獲取一堆的Cookie,然后從這堆Cookie中每次隨機取一個Cookie,再通過這個Cookie去訪問。那我們就需要一個新的東西:Cookie池。
我們看下神箭手如何調用Cookie池,首先我們需要新建一個爬蟲應用專門用於登錄:
var configs = {
shareUserWithKey:"bindex",
};
configs.onUserAdded = function (use, psw, site) {
var loginResult = login(use, psw, site);
if( loginResult !="success") {
return false;
}
return true;
}
這里的login方法就是剛剛我們寫的百度登錄,然后我們再在beforeCrawl的回調函數中反復調用以下方法:
site.addUser(user, password);
當然這里還有一個問題,如果我們一直使用一個IP來登錄,也很容易被百度封掉,所以我們最好打開企業代理IP接入。
通過這種形式我們就可以建立一個可共享的Cookie池。然后我們在百度指數API應用(下一章會詳細介紹)里通過設置以下代碼來共享這個Cookie池:
var configs = {
shareUserWithKey:"bindex",
multiUser: true
}
這樣在這個應用中會在每次訪問一個Url的周期中隨機從Cookie池取一個Cookie並請求Url。通過這種形式我們還可以把登錄和請求代碼解耦合。將來還可以復用登錄代碼。
第三節:問題來了,帳號從哪來呢?
除了把七大姑八大姨的手機都來注冊一遍以外,沒什么好辦法,除非…(此處省略1000個字)。
第二章:獲取指數圖片API
第一節:異步請求數據
終於完成了登錄,感覺怎么樣,是不是有點天坑的意思?哈哈,萬里長征咱才走了第一步。下面我們才真正來揭開天坑的核心:數字圖片。
然后我們繼續掀被子看看這個標簽是怎么來的:
貌似不難找,不過看這個URL看着就頭大,感覺已經被百度登錄傷害過一次之后真的無力再一個一個參數分析,我們直接使用神箭手提供的js渲染頁面的接口,直接把頁面渲染出來把:
var configs = {
domains: ["index.baidu.com"],
scanUrls: ["http://index.baidu.com/?tpl=trend&word=" + encodeURI(keyword, "GBK")],
enableJS:true
}
這樣我們在afterDownloadPage中拿到的就直接是渲染好的頁面了,我們再通過正則和XPath取出數字圖片的容器標簽代碼和Css代碼(Css代碼就是把圖片設置成背景的style標簽),之所以要拿Style標簽是因為兩個數字圖片共享了一個Style,而這個Style在第一個數字圖片的標簽中,所以我們必須抽取出這個Style標簽,在分別設置給兩個不同的數字的容器標簽代碼。這段代碼咱們再下一節中給出。
第二節:渲染數據成圖片
我們拿到了數字圖片的容器標簽代碼有什么用呢,當然是要渲染出對應的圖片了。那為什么我們要這么大的彎去得到這張圖呢。這一點正是百度指數能當選天坑的原因了,我們看一下這個圖片是如何拼出來的,我們看下這段HTML代碼。
而最最最變態的是,兩個標簽並不是兩個數字,而是三個數字!這就說明我們不可能一個一個數字去識別,必須作為一個整體圖片來識別了。
我們知道PhantomJS這類Headless的瀏覽器都有渲染Html代碼成圖片的功能,神箭手渲染JS基於PhantomJS當然也支持這個功能,而且我們的調用接口更簡單只需要調用site.renderImage方法既可實現將代碼渲染成圖片的功能,
下面是結合第一節的完整代碼如下:
var sevenReg = /class="mtable profWagv">(.+?)</table>/;
var sevenMatch = sevenReg.exec(indexInfo);
if(sevenMatch) {
var sevenInfo = extractList(sevenMatch[1],"//*[@class='ftlwhf enc2imgVal']");
var styleReg = /(
if(styleMatch) {
var html = sevenHtml + sevenInfo[0].substring(0,sevenInfo[0].length-7) +styleMatch[1] + "
var base64 = site.renderImage(html, 120, 25);
index.week_all_index = base64ToImg(base64,site);
var html2 = sevenHtml + sevenInfo[1].substring(0,sevenInfo[1].length-7) +styleMatch[1] + "