微博爬蟲高級篇——自動獲取微博cookie(無須賬號、每日百萬量級)

一、前言

事先說明,博主今年剛畢業,計算機應用技術,專科。沒錯,語文很差的那種。這是第一次幹這種文章編輯,再加上年紀小,什麼語法、錯別字之類,前後語句不通順啊什麼的都無視吧。如果有什麼問題可以找貼吧找我吧,我一般在java吧混,貼吧賬號:ZSsanguosha

二、想說的話

 現在市面有很多大數據或者做輿情分析的公司。這些公司難免都要用到爬蟲,被爬得最多的,大概也是就微博了。雖然微博有自己的商業接口可以提供數據,但是裏面有很多限制,比如:頻次,字段缺失不滿足需求。尤其 是頻次,我之前的公司裏就幾個商業接口,大家共用,頻次限制死死的,採集量大的時候完全用不過來。

所以,這時候就需要爬蟲了,但是微博反爬機制是真的秀,所有內容都是FW.view()填充,動態cookie加密,驗證碼、封IP等等,尤其是搞了一個Sina Visitor System(新浪訪客系統)。如果沒有微博的cookie,爬蟲取回來的頁面全部變成Sina Visitor System了。微博cookie,必須登錄後才能獲取,而且你爬取過多,賬號就會被凍結。

用cookie爬,容易被凍結賬號,而且也實現不了自動化;不用cookie,就是Sina Visitor System頁面,根本爬不到內容。博主這邊就研究瞭如何跳過這個系統

三、致敬大佬

之前我接手公司某個需求,需要爬微博頁面,數據量級大致在二千萬左右。光用商業接口是根本來不及的。還好,有萬能的搜索引擎告訴我有這麼一篇文章

 

 

 

 

 

我很想放大佬原貼地址,但是有太多盜貼,分辨不了。原貼中具體的實現方式由於微博的更新,已經不可用了,但是還有一幫人盜貼,實在是看不下去, 所以,我打算髮這篇博客糾正一下

四、基本原理

Sina Visitor System(新浪訪客系統),在請求微博頁面判斷有沒有微博cookie?有,就跳轉,沒有且不是爬蟲,就創造一個遊客cookie,以便訪問。

博主用微博的“發現-找人-領域頁”(https://d.weibo.com/1087030002_2975_2017_0)來舉例,“1087030002_2975_2017_0”是這個頁面的路徑名,第一次是302重定向,然後經過一系列的請求跳轉,最後變成200。

 

 

 

 

稍微比較一下兩個“1087030002_2975_2017_0”請求的區別,請求頭信息都差不多。最大的不同是,第一次請求是set-cookie,也就是沒有cookie的值,第二次多了cookie,裏面有三個值YF-Page-G0、SUB、SUBP。也就是說,這個cookie就是我們需要獲取的遊客cookie。

另外,以谷歌瀏覽器爲例,想看到這些請求,先清空緩存(主要是cookie),F12打開開發者模式,在Network下面勾選 Preserve log,最後ctrl+R 重新加載

 

 

 

 

接下來,我們具體討論下,他是怎麼設置cookie的。先將第二個請求複製出來,在Postman中SEND一下。你會發現這個就是Sina Visitor System(新浪訪客系統)。而且他還用中文註釋,這是赤果果的挑釁(ノ`⊿´)ノ。另外它在body標籤中,導入了一個js文件,也就是我們的第三個請求


我們一下看到,incarnate()方法是給用戶賦予訪客身份的。它發送了一個get請求,一對比,發現這是上面的第6個請求。也就是說,我們能成功發送第6個請求,就能成功獲取cookie。

 

 

 

通過js我們可以知道,發送第6個請求,以下幾個參數:

a、t、w、c、gc、cb、from、_rand。 其中a、cb、from是定值,_rand是隨機數。

以下是第6個請求的詳情,gc是空的,也就是說,我們只需要知道t(tid)、w(where)、c(conficence)三個參數就可以了

博主測試過,發送請求時gc填不填無所謂,一樣能成功

這三個參數這裏是沒有,只能通過mini_original.js(第三個請求)去找。1984行的源碼。上面有三個關鍵詞,用tid一搜,就找到了。

如圖:w(where) -> recover,看來w(where)在這裏等價於recover

接下來,就是整理源碼。往下翻一下,我們可以找到這個方法,這個明顯就是獲取tid的方法。跟Network裏的請求比對一下,發現這就是第5個請求。

這是一個POST請求,傳了兩個值,一個是cp,定值爲:gen_callback;另一個是fp,他是通過 getFp()方法生成,博主看了下,大致是獲取瀏覽器類型,窗口大小,字體之類的常量。應該是爲了判斷是否爲爬蟲設置的。也就是說,只要不改瀏覽器配置,這些值不會變的。測試的時候,直接複製進去就可以了

爲了方便截圖,博主改變了js代碼的部分位置,但具體實現是不變的

然後,我們用postman發送模擬請求 https://passport.weibo.com/visitor/genvisitor(第五個請求),他的返回值爲

{
	"retcode": 20000000,
	"msg": "succ",
	"data": {
		"tid": "O8DdOkekzzLgrDM2e0HhvBRePB8ZVty6FeowFyc7IR0=",
		"new_tid": true
	}
}

tid就找到了,只剩w(where)和c(conficence),我們繼續看源碼,發現他在後面回調的時候進行了處理。w(where) 在"new_tid"爲true的時候是3,false的時候是2。 

c(conficence) 可能有,可能沒有,沒有默認爲100

雖然我這裏沒有,但是 data下面有機率出現一個叫 “conficence”的字段,我測試的時候他的值都是95

 

 

 

 

至此,我們找到了發送第6個請求(https://passport.weibo.com/visitor/visitor?a=incarnate&xxxxxxxx)的全部參數,模擬發下請求。得到結果。ok,sub和subp就全部得到。

window.cross_domain && cross_domain({
	"retcode": 20000000,
	"msg": "succ",
	"data": {
		"sub": "_2AkMsBM0Wf8NxqwJRmfgQzm_laoR-yg3EieKaWDzNJRMxHRl-yT83qn04tRB6B4Tj-ZvOcFzfsmjrLJjxv39RkzOyvMzE",
		"subp": "0033WrSXqPxfM72-Ws9jqgMF55529P9D9Whhkx2zn2ycSbRz3ZvmBTfm"
	}
});

把這兩個參數,加上最開始的YF-Page-G0,就可以得到完整的遊客cookie。將這個cookie填入請求頭,再次發起https://d.weibo.com/1087030002_2975_2017_0。完美成功

這裏,博主測試的時候,cookie中不加入YF-Page-G0,無法獲取到值。但大佬的文章表示不需要這個值。請自行斟酌;elipse的console是有輸出限制,所以只能顯示最後幾行。另外,微博的頁面內容都是用FM.view()填充的。 

五、java實現

上面的實現是面向所有語言的,因此,上面並沒有放代碼。下面給大家放一個java的實現版本。博主會java,還有python。爬蟲一般用Python比較合適,但這次選用java。沒有什麼理由,就是懶。java版本的之前完成需求的時候就已經寫好了,ctrl cv 就夠了。python還得從頭開始寫,太懶了~~

接下來的代碼,都是從原有項目裏拆分的,我稍微改造了下,有些冗餘我就不優化了。不用照抄,有些實現完全就是無用 的

ok,第一步,maven依賴導入jar包。博主使用的連接工具用httpclient,解析html頁面用jsoup,各位自行選用合適的就好。具體依賴如下

<dependency>
  	<groupId>org.jsoup</groupId>
  	<artifactId>jsoup</artifactId>
 	<version>1.10.1</version>
</dependency>
<dependency>
	<groupId>org.apache.httpcomponents</groupId>
  	<artifactId>httpclient</artifactId>
  	<version>4.5.5</version>
</dependency>

第二步,連接工具類

博主這邊加入了ssl校驗、代理IP、cookie,一些連接配置,顯得有些複雜了

/**
	 * @Title: generateClient 
	 * @Description: TODO(增加代理) 
	 * @param httpHost
	 * @return
	 *     CloseableHttpClient 返回類型
	 */
	public static CloseableHttpClient generateClient(HttpHost httpHost,CookieStore cookieStore) {
		SSLContext sslcontext = SSLContexts.createSystemDefault();
		Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
				.register("http", PlainConnectionSocketFactory.INSTANCE)
				.register("https", new SSLConnectionSocketFactory(sslcontext)).build();
		// http連接池管理,服務於多個執行進程的連接請求
		PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(
				socketFactoryRegistry);
		connectionManager.setMaxTotal(200);
		connectionManager.setDefaultMaxPerRoute(20);
		
		RequestConfig requestConfig = RequestConfig.custom().setProxy(httpHost).build();
		
		HttpClientBuilder httpClientBuilder = HttpClients.custom().setUserAgent(randomUserAgent())
				.setConnectionManager(connectionManager).setDefaultRequestConfig(requestConfig).setDefaultCookieStore(cookieStore);
		return httpClientBuilder.build();
	}

第三步,獲取t、w、c三個參數的值

private JSONObject getTidAndC() throws IOException
	{
		String url = "https://passport.weibo.com/visitor/genvisitor";
		HttpPost httpPost = createHttpPost(url);
		CloseableHttpResponse response = httpclient.execute(httpPost);
		HttpEntity entity = response.getEntity();  
        if (entity != null) {  
            //按指定編碼轉換結果實體爲String類型  
            String body = EntityUtils.toString(entity, "utf-8");
            body = body.replaceAll("window.gen_callback && gen_callback\\(", "");
            body = body.replaceAll("\\);", "");
            JSONObject json = JSONObject.fromObject(body).getJSONObject("data");
            System.out.println(body);
           
            return json;
        }
        return null;
	}

第四步獲取cookie

public String getCookie() throws IOException {
		JSONObject json = getTidAndC();
		String t = "";
		String w = "";

		String c = json.containsKey("confidence") ? json.getString("confidence") : "100";
		if (json.containsKey("new_tid"))
		{
			w = json.getBoolean("new_tid") ? "3" : "2";
		}
		if (json.containsKey("tid"))
		{
			t = json.getString("tid");
		}
		System.out.println(c);
		String url = "https://passport.weibo.com/visitor/visitor?a=incarnate&t="+t+"&w="+w+"&c=0"+c+"&gc=&cb=cross_domain&from=weibo&_rand="+Math.random();
		HttpGet httpGet = createCookieGet(url, "tid="+t+"__"+c);
		CloseableHttpResponse response = httpclient.execute(httpGet);
		HttpEntity httpEntity = response.getEntity();
		String body = EntityUtils.toString(httpEntity, "utf-8");
		System.out.println(body);
		body = body.replaceAll("window.cross_domain && cross_domain\\(", "");
		body = body.replaceAll("\\);", "");
		
		JSONObject obj = JSONObject.fromObject(body).getJSONObject("data");
		System.out.println(obj.toString());
		String cookie = "YF-Page-G0="+getYF()+"; SUB="+obj.getString("sub")+"; SUBP="+obj.getString("subp");
		System.out.println("cookie:  "+cookie);
		httpclient.close();
		return cookie;
	}

 YF-Page-G0 的參數,通過發送請求,獲取set_cookie的值得到的。這樣就獲得完整的遊客cookie,

應該是把url加入參數列表比較合理,值也不應該寫死

public String getYF() throws IOException {
		String domain = "1087030002_2975_5012_0";
		String url = "https://d.weibo.com/"+domain;
		HttpGet httpGet = createHttpGet(url, null);
		CloseableHttpResponse response = httpclient.execute(httpGet);
		
		List<Cookie> cookies = cookieStore.getCookies();
		String str = "";
		for (Cookie cookie : cookies)
		{
			str = cookie.getValue();
		}
		return str;
	}

最後,就是調用了。

只要調用getCookie(),就能獲取完整的遊客cookie。然後,就能獲取頁面數據

我想你們已經發現了,這幾個方法都是博主以前做測試留下的測試文件,實在懶得改就隨意了。下列代碼的運行結果,上面已經發了,就不再發了。

@Test
	public void test(){
		String domain = "1087030002_2975_2013_0";
		try {
			//獲取cookie			
			String cookie = getCookie();
			//將cookie傳入請求頭,生成get請求
			String url = "https://d.weibo.com/"+domain;
			HttpGet httpGet = createHttpGet(url, cookie);
			//獲取響應結果,輸出
			CloseableHttpClient httpclient = HttpClients.custom().build();
			CloseableHttpResponse response = httpclient.execute(httpGet);
			HttpEntity httpEntity = response.getEntity();
			String body = EntityUtils.toString(httpEntity, "utf-8");
			//html
			System.out.println(body);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

六、注意事項

微博的大部分頁面都可以用這個方法爬到,搜索和評論沒辦法拿到全部數據。上限好像是20頁?

該方法極其容易被封ip,大規模使用時必須要設置代理ip

同一個ip,獲取cookie,每5次 可能會失敗一次,自行做好判斷

截止  2018年7月25日 21:18:24,方法並未失效,之後我就不知道了

七、多線程自動化微博爬蟲

這個具體實現代碼不能說太多,公司項目已經上線,保密協議生效中,博主也就不給自己找麻煩。大抵的思路,就是設置多個線程,提供不同的代理ip,做個錯誤提醒,異常處理,設置合理的頻次讓IP存活更久之類的常規操作。

博主曾經開過8個線程,一天大致能爬200w左右的數據量。以後可能會發幾篇微博爬蟲的基本操作,這方面的話,等什麼時候保密協議過了,再繼續更新吧

八、結尾

轉貼,留個全屍就可以了,標明出處和作者,,其餘隨意

有什麼問題可以聯繫我,或者有什麼錯誤。歡迎斧正。最後,csdn不一定能聯繫到我,樓主一般出沒在java吧,貼吧ID:ZSsanguosha。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章