1. 程式人生 > >Java爬蟲入門簡介(四)——抓包工具的使用以及使用HttpClient模擬使用者登入的訪問

Java爬蟲入門簡介(四)——抓包工具的使用以及使用HttpClient模擬使用者登入的訪問

網路爬蟲需要解決的一個重要的問題就是要針對某些需要使用者名稱和密碼訪問的頁面可以模擬使用者自動登入。在這一篇部落格中我們將介紹如何使用Chrome瀏覽器自帶的抓包工具分析頁面並模擬使用者自動登入。我們會以CSDN的使用者登入為例,講述如何使用抓包工具獲取登入方式並使用HttpClient工具模擬登陸訪問。在之前的部落格中我們已經講述瞭如下內容:

抓包工具

抓包工具是指那些可以捕獲網路傳送的資料包的工具。網站的登入是客戶端瀏覽器與伺服器端的通訊。很多時候,很多時候有價值的網頁都需要使用者登入之後才能瀏覽。而登入的過程一般是向伺服器某個地址傳送請求,並將使用者名稱和密碼等引數一同傳送給伺服器驗證。這個地址通常都是我們在正常瀏覽網站的時候看不到的。因此需要抓包工具獲取並分析。在這篇部落格中我們將講述使用Chrome自帶的抓包工具進行分析。

首先我們需要使用瀏覽器訪問:https://passport.csdn.net/account/login 頁面。這是CSDN的登入頁面,然後我們滑鼠右擊“登入”按鈕,選擇“檢查(N)”,這樣就打開了Chrome瀏覽器自帶的開發者工具了。

在下面的工具欄中我們可以看到很多個標籤頁,依次是“Element” - “Console” - “Sources” - “Network” - “Performance” 等等。我們最常用的是前面四個,後面就不列舉了。
Elements是展示頁面的原始碼的,就是這個網頁裡面HTML/CSS/JavaScript等原始碼。滑鼠點選到任何一個標籤的時候會在右側展示出幾個子標籤,如上圖所示,在爬蟲中我們最常用的是選擇Event Listeners標籤,因為通常我們需要根據頁面的某個元素的事件尋找處理的JavaScript程式碼,並在程式碼中找到一些請求的結果。

接下來我們開啟Network標籤,Network主要就是展示瀏覽器與伺服器之間傳送請求的頁面了。注意到,在抓包的時候,如圖所示,第一個紅色方框裡面我們通常要勾選Preserve log。因為頁面一旦跳轉,之前的頁面與伺服器之間傳送的資料就會丟失,但我們需要分析這些資料,所以一般還是需要儲存下來請求的日誌。下面的方框主要包含幾類請求的分類,如 XHR JS CSS Doc等。在爬蟲中,我們一般只會用到請求Doc頁面和XHR的內容,前者一般就是請求的一個完整頁面,XHR一般是Ajax請求的結果。

CSDN登入分析

簡單瞭解之後我們現在開始分析CSDN的登入。首先開啟上述開發者工具,切換到Network標籤,勾選Preserve log。然後點選登入。然後我們看到工具欄裡面出現了很多的請求,我們先從Doc標籤下看有沒有我們需要的。我們看到有一個請求了”login”的地址。這一般就是登入請求的URL了。我們點開這個地址後看到右側展開了如下內容:


首先第一塊是General,這裡展示的是一般的請求情況。我們看到:
Request URL是請求的地址
Request Method是指請求的方式,這裡是Post(當然登入一般都是Post)
Status Code:請求的情況,就是狀態,200表示沒有任何問題。
剩下的兩個一般沒啥意義不說了。

這裡看到了請求,再把這個標籤也拉到最底下我們看到:

Request Headers這是我們請求的頭部資訊了。在之前的部落格中我們已經說明了如何使用帶頭部的HttpClient請求了。一般情況下,頭部都要寫對,不然很容易出錯。
Query String Parameters:這個是請求的引數,一般情況下請求的引數都在這裡,和下面的Form Data其實是一個意思。只不過一個是通過地址引數,一個是通過表單提交的,這些都是我們需要獲得的引數提交。我們把這個標籤看完整,如下圖所示:

我們看到在登入CSDN的時候其實我們是傳送了好多引數,並不是只有使用者名稱和密碼。一般情況下都會有些額外的識別符號表明使用者是正常訪問他們的頁面並提交驗證請求的。這裡的引數分別如下:
ref: toobar這個是表明ref的地址,不用管。不寫也是可以的(因為我是退出後點擊登入,所以有這個,你們直接輸入上面的地址應該是沒有的)。
username:這是使用者名稱,我這裡打碼了,是我的郵箱
password:這個是密碼。通常密碼很有可能是加密的,那就需要我們寫個加密程式,一般都是利用頁面的一個公鑰來加密,但是CSDN真是的。。太懶了。
lt/execution/_eventId:這個是頁面的一些程式碼,用來註明是使用者訪問了這個頁面才登入的。後面我們會描述怎麼找這些引數。

尋找登入引數

一般來說上面這三個引數有兩種方式生成,一種是直接在頁面新增幾個隱藏的元素,把這些值放到這些元素中,然後直接通過JavaScript取出來,並連同使用者名稱和密碼傳送。還有種方式也是類似的,但是JavaScript處理的時候會做一些簡單的運算,比如做個轉換啥的,這樣也增加了爬蟲的困難。後一種方式可以通過對JavaScript新增斷點尋找。也很簡單,就是通過上面右鍵單擊登入按鈕,然後找到這個按鈕監聽的事件對應的JavaScript,然後在裡面追蹤即可。以後有機會我們舉個例子。這裡很簡單,我們就直接能在頁面找到。我們在Elements的標籤下看到如下的內容:

這裡也就是說我們可以直接通過Jsoup解析這個頁面就能得到這些引數了。

使用HttpClient登入

通過上面的分析我們可以看到登入CSDN的過程應該是:
1、訪問https://passport.csdn.net/account/login 頁面
2、解析上述頁面,並獲取頁面的引數lt/execution/_eventId
3、同用戶名和密碼一起,將這些引數傳送給伺服器驗證。

那麼我們把核心程式碼放到下面(注意,這裡CSDN都是用Https訪問的,所以不能像之前那樣直接使用HttpClient,要使用SSL的訪問方式)。核心程式碼如下:

1、初始化一個可以訪問Https的HttpClient

(這個有機會我們再說,這裡按照下面的方式初始化即可,我們使用的是免證書驗證的方式)

X509TrustManager xtm = new HFUT509TrustManager();
SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(new TrustSelfSignedStrategy()).build();
sslcontext.init(new KeyManager[0], new TrustManager[] {xtm }, new SecureRandom());
sslcontext.init(null, new X509TrustManager[]{xtm}, new SecureRandom());

//注意,這個右邊是Lambda表示式,我們用的是jdk1.8
SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslcontext, (s, sslSession) -> true); 

Registry r = RegistryBuilder. create().register("https", factory).build();

PoolingHttpClientConnectionManager connPool = new PoolingHttpClientConnectionManager(r);
connPool.setMaxTotal(200);
connPool.setDefaultMaxPerRoute(20);

CloseableHttpClient httpClient = HttpClients.custom().
            setConnectionManagerShared(true).
            setConnectionManager(connPool).
            setSSLSocketFactory(factory).build();
2、訪問登入頁面獲取引數
HFUTRequest hfutRequest = new HFUTRequest();
hfutRequest.getSSLClient();

String loginPage = "https://passport.csdn.net/account/login";
String loginPageContent = hfutRequest.getHTMLContentByHttpGetMethod(loginPage);

Document document = Jsoup.parse(loginPageContent);

//一般我們直接使用上面的登陸地址就好,CSDN在他的form表單中給出了請求地址,這裡是解析的表單的action值獲取地址的,大家可以之間用上面抓包結果的地址
String loginRequestURL = document.select("form#fm1").attr("action");
loginRequestURL = "https://passport.csdn.net"+loginRequestURL+"";

//獲取三個引數
String lt = document.select("input[name=lt]").attr("value");
String execution = document.select("input[name=execution]").attr("value");
String _eventId = document.select("input[name=_eventId]").attr("value");
3、請求登入驗證
List<NameValuePair> nameValuePairList = Lists.newArrayList();
nameValuePairList.add(new BasicNameValuePair("username", username));
nameValuePairList.add(new BasicNameValuePair("password", password));
nameValuePairList.add(new BasicNameValuePair("lt", lt));
nameValuePairList.add(new BasicNameValuePair("execution", execution));
nameValuePairList.add(new BasicNameValuePair("_eventId", _eventId));

List<Header> headerList = Lists.newArrayList();
headerList.add(new BasicHeader(HttpHeaders.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"));
headerList.add(new BasicHeader(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate, br"));
headerList.add(new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4,ja;q=0.2,de;q=0.2"));
headerList.add(new BasicHeader(HttpHeaders.CACHE_CONTROL, "max-age=0"));
headerList.add(new BasicHeader(HttpHeaders.CONNECTION, "keep-alive"));
headerList.add(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded"));
headerList.add(new BasicHeader("DNT","1"));
headerList.add(new BasicHeader(HttpHeaders.HOST, "passport.csdn.net"));
headerList.add(new BasicHeader("Origin", "https://passport.csdn.net"));
headerList.add(new BasicHeader(HttpHeaders.REFERER, "https://passport.csdn.net/account/login?from=http://my.csdn.net/my/mycsdn"));
headerList.add(new BasicHeader("Upgrade-Insecure-Requests","1"));
headerList.add(new BasicHeader(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36"));

HttpPost httpPost = new HttpPost(loginRequestURL);
for( Header header : headerList ){
    httpPost.addHeader(header);
}

String content = EntityUtils.toString(httpClient.execute(httpPost).getEntity());
content = hfutRequest.getHTMLContentByHttpPostMethod(loginRequestURL, nameValuePairList);
System.out.println(content);

最後我們看到了如下的結果證明我們獲取是對的。