原文地址:http://blog.shenjianshou.cn/?p=170
giithub:https://github.com/TTyb/Baiduindex
最近有不少朋友跟我說,「爬蟲這東西很簡單啊,好像還沒學就已經會了,沒啥深奧的東西哦。看了你以前的教程,不就是一個隊列加一些Http請求嗎,不就是寫寫XPath和正則嗎,大家還作個神箭手雲爬蟲出來?我本身上個廁所就寫完了啊。」php
看來是時候拿出咱們壓箱底多年的老乾媽了,哦不,老幹貨了。不嚇屎大家這羣小學生我就不在6年級混了。html
廢話很少說,所謂爬蟲天坑,敢對得起這個名字的必定不能是等閒之輩。起碼得是過完年老闆給你扔這個任務,你兒童節還在頭大的級別。今天第一課,我們就先找個最難的熱熱身吧:爬取百度指數的關鍵詞搜索指數。git
先貼一個logo讓你們跪拜一下github
好了,你們平身吧,我們立刻就正式開始了,想上廁所的趕忙去,否則看完這篇文章估計你就忘了怎麼上廁所了。api
正式開始以前,先插個廣告:若是土豪朋友不想寫代碼或者中途看不下去的,咱們將如下代碼已經打包成一個完整的應用,你們進入神箭手的雲市場搜索百度指數(http://www.shenjianshou.cn/index.php?r=market/product&product_id=500036)就能夠看到應用,直接調用既可。瀏覽器
——————————–前方高能預警看也看不完上廁所趕忙去分割線————————————-
我們正式開始:所謂知己知彼百戰不殆,咱們要先了解一下咱們的對手。我們打開百度指數
http://index.baidu.com,映入眼簾的是一個簡單的輸入框。好開心啊,好像不用登陸啊,輸入一個關鍵字試一下吧,輸入神箭手,回車:cookie
果真百度老司機不會讓咱們那麼開心的。沒事沒事,不就是登陸嗎,也不是沒作過登陸,抓包研究下請求應該不難。咱們先找一個帳號登陸看下。登陸以後繼續輸入神箭手:less
出來了。哈哈,不難嘛,這不就直接顯示了。而後就按照之前的爬蟲的教程,用XPATH獲取一下數字就能夠了,哈哈哈…哈哈..哈……..dom
慢着,怎麼感受這個數字看着怪怪。嚇得我趕忙掀開被子看看這貨究竟是啥:異步
什麼?這是圖!!!!什麼?這仍是拼圖!!!!什麼?這貨竟然是異步的拼圖!!!!
怎麼樣,感覺到天坑的深度沒有?
那我們就一塊兒來看看怎麼見招拆招,用神箭手把百度指數搞定的吧。
開始具體的代碼以前,咱們先在神箭手後臺新建三個應用,分別是百度指數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 = /(