1. 程式人生 > >微博爬蟲“免登入”技巧詳解及 Java 實現

微博爬蟲“免登入”技巧詳解及 Java 實現

一、微博一定要登入才能抓取?

目前,對於微博的爬蟲,大部分是基於模擬微博賬號登入的方式實現的,這種方式如果真的運營起來,實際上是一件非常頭疼痛苦的事,你可能每天都過得提心吊膽,生怕新浪爸爸把你的那些賬號給封了,而且現在隨著實名制的落地,獲得賬號的渠道估計也會變得越來越少。

但是日子還得繼續,在如此艱難的條件下,為了生存爬蟲們必須尋求進化。好在上帝關門的同時會隨手開窗,微博在其他諸如頭條,一點等這類新媒體平臺的衝擊之下,逐步放開了資訊流的檢視許可權。現在的微博即便在不登入的狀態下,依然可以看到很多微博資訊流,而我們的落腳點就在這裡。

作為前篇的重點內容,本文詳細介紹如何獲取相關的Cookie並重新封裝Httpclient達到免登入的目的,以支援微博上的各項資料抓取任務。下面就從微博首頁http://weibo.com開始。

二、準備工作

準備工作很簡單,一個現代瀏覽器(你知道我為什麼會寫”現代”兩個字),以及httpclient(我用的版本是4.5.3)

跟登入爬蟲一樣,免登入爬蟲也是需要裝載Cookie。這裡的Cookie是用來標明遊客身份,利用這個Cookie就可以在微博平臺中訪問那些允許訪問的內容了。

這裡我們可以使用瀏覽器的network工具來看一下,請求http://weibo.com之後伺服器都返回哪些東西,當然事先清空一下瀏覽器的快取。

不出意外,應該可以看到下圖中的內容

第1次請求weibo.com的時候,其狀態為302重定向,也就是說這時並沒有真正地開始載入頁面,而最後一個請求weibo.com的狀態為200,表示了請求成功,對比兩次請求的header:

明顯地,中間的這些過程給客戶端載入了各種Cookie,從而使得可以順利訪問頁面,接下來我們逐個進行分析。

三、抽絲剝繭

第2個請求是https://passport.weibo.com/visitor……,各位可以把這個url複製出來,用httpclient單獨訪問一下這個url,可以看到返回的是一個html頁面,裡面有一大段Javascript指令碼,另外頭部還引用一個JS檔案mini_original.js,也就是第3個請求。指令碼的功能比較多,就不一一敘述了,簡單來說就是微博訪問的入口控制,而值得我們注意的是其中的一個function:

JavaScript
1234567 // 為使用者賦予訪客身份 。varincarnate=function(tid,where,conficence){vargen<em>conf="";varfrom="weibo";varincarnate_intr=window.location.protocol+"//"+window.location.host+"/visitor/visitor?a=incarnate&t="+encodeURIComponent(tid)+"&w="+encodeURIComponent(where)+"&c="+encodeURIComponent(conficence)+"&gc="+encodeURIComponent(gen_conf)+"&cb=cross_domain&from="+from+"&_rand="+Math.random();url.l(incarnate_intr);};</em>

這裡是為請求者賦予一個訪客身份,而控制跳轉的連結也是由一些引數拼接起來的,也就是上圖中第6個請求。所以下面的工作就是獲得這3個引數:tid,w(where),c(conficence,從下文來看應為confidence,大概是新浪工程師的手誤)。繼續閱讀原始碼,可以看到該function是tid.get方法的回撥函式,而這個tid則是定義在那個mini_original.js中的一個物件,其部分原始碼為:

JavaScript
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970 vartid={key:'tid',value:'',recover:0,confidence:'',postInterface:postUrl,fpCollectInterface:sendUrl,callbackStack:[],init:function(){tid.get();},runstack:function(){varf;while(f=tid.callbackStack.pop()){f(tid.value,tid.recover,tid.confidence);//注意這裡,對應上述的3個引數}},get:function(callback){callback=callback||function(){};tid.callbackStack.push(callback);if(tid.value){returntid.runstack();}Store.DB.get(tid.key,function(v){if(!v){tid.getTidFromServer();}else{……}});},……}……getTidFromServer:function(){tid.getTidFromServer=function(){};if(window.use<em>fp){getFp(function(data){util.postData(window.location.protocol+'//'+window.location.host+'/'+tid.postInterface,"cb=gen_callback&fp="+encodeURIComponent(data),function(res){if(res){eval(res);}});});}else{util.postData(window.location.protocol+'//'+window.location.host+'/'+tid.postInterface,"cb=gen_callback",function(res){if(res){eval(res);}});}},……//獲得引數window.gen_callback=function(fp){varvalue=false,confidence;if(fp){if(fp.retcode==20000000){confidence=typeof(fp.data.confidence)!='undefined'?'000'+fp.data.confidence:'100';tid.recover=fp.data.new_tid?3:2;tid.confidence=confidence=confidence.substring(confidence.length-3);value=fp.data.tid;Store.DB.set(tid.key,value+'</em>'+confidence);}}tid.value=value;tid.runstack();};

顯然,tid.runstack()是真正執行回撥函式的地方,這裡就能看到傳入的3個引數。在get方法中,當cookie為空時,tid會呼叫getTidFromServer,這時就產生了第5個請求https://passport.weibo.com/visitor/genvisitor,它需要兩個引數cb和fp,其引數值可以作為常量:

該請求的結果返回一串json

JavaScript
123456789 {"msg":"succ","data":{"new_tid":false,"confidence":95,"tid":"kIRvLolhrCR5iSCc80tWqDYmwBvlRVlnY2+yvCQ1VVA="},"retcode":20000000}

其中就包含了tid和confidence,這個confidence,我猜大概是推測客戶端是否真實的一個置信度,不一定出現,根據window.gen_callback方法,不出現時預設為100,另外當new_tid為真時引數where等於3,否則等於2。

此時3個引數已經全部獲得,現在就可以用httpclient發起上面第6個請求,返回得到另一串json:

JavaScript
12345678 {"msg":"succ","data":{"sub":"_2AkMu428tf8NxqwJRmPAcxWzmZYh_zQjEieKYv572JRMxHRl-yT83qnMGtRCnhyR4ezQQZQrBRO3gVMwM5ZB2hQ..","subp":"0033WrSXqPxfM72-Ws9jqgMF55529P9D9WWU2MgYnITksS2awP.AX-DQ"},"retcode":20000000}
參考最後請求weibo.com的header,這裡的sub和subp就是最終要獲取的cookie值。大家或許有一個小疑問,第一個Cookie怎麼來的,沒用嗎?是的,這個Cookie是第一次訪問weibo.com產生的,經過測試可以不用裝載。

最後我們用上面兩個Cookie裝載到HttpClient中請求一次weibo.com,就可以獲得完整的html頁面了,下面就是見證奇蹟的時刻:

XHTML
12345678910111213141516171819202122232425262728293031323334353637 <!doctype html><html><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible"content="IE=edge,chrome=1"><meta name="viewport"content="initial-scale=1,minimum-scale=1" /><meta content="隨時隨地發現新鮮事!微博帶你欣賞世界上每一個精彩瞬間,瞭解每一個幕後故事。分享你想表達的,讓全世界都能聽到你的心聲!"name="description" /><link rel="mask-icon"sizes="any"href="//img.t.sinajs.cn/t6/style/images/apple/wbfont.svg"color="black" /><link rel="shortcut icon"type="image/x-icon"href="/favicon.ico" /><script type="text/javascript">try{document.execCommand("BackgroundImageCache",false,true);}catch(e){}</script><title>微博-隨時隨地發現新鮮事</title><link href="//img.t.sinajs.cn/t6/style/css/module/base/frame.css?version=6c9bf6ab3b33391f"type="text/css"rel="stylesheet"charset="utf-8" /><link href="//img.t.sinajs.cn/t6/style/css/pages/growth/login_v5.css?version=6c9bf6ab3b33391f"type="text/css"rel="stylesheet"charset="utf-8"><link href="//img.t.sinajs.cn/t6/skin/default/skin.css?version=6c9bf6ab3b33391f"type="text/css"rel="stylesheet"id="skin_style" /><script type="text/javascript">var$CONFIG={};$CONFIG['islogin']='0';$CONFIG['version']='6c9bf6ab3b33391f';$CONFIG['timeDiff']=(newDate()-1505746970000);$CONFIG['lang']='zh-cn';$CONFIG['jsPath']='//js.t.sinajs.cn/t5/';$CONFIG['cssPath']='//img.t.sinajs.cn/t5/';$CONFIG['imgPath']='//img.t.sinajs.cn/t5/';$CONFIG