PS:恩,由于最近在學web和簡單的http協議,所以心血來潮想用java寫個爬蟲來爬取學校官網(正方教務系統)個人主頁的基礎信息(課程信息、成績……),其實在之前學過java基礎教程的時候就可以寫的,但后知后覺總是在當前階段做之前階段應該完成的任務!或許這就是菜鳥吧~~~。
?
寫在前面:其實寫java爬蟲的話,一般使用三種方法:自帶庫類(urlconnection),外庫(httpclient)和文檔解析庫(jsoup);其中jsoup這個第三方庫重點用于對html文檔的解析,在官方文檔中除了Jsoup.connect()方法進行訪問網頁外并沒有多余的方法,而且在訪問網頁是需要登錄的網站爬蟲中(get和post方法訪問),大多使用前兩種方法。又由于httpclient相對于自帶庫更加靈活高效,所以在該爬蟲中使用httpclient訪問+Jsoup解析文檔結合操作。
?
注意:作為一個小白,在嘗試每一個別人已經實現的而自己未曾實現的項目時,都會有或多或少的問題,而這個爬蟲項目對于我來說也是如此,所以才會寫下這篇博文作為填坑的記錄,也方便別的剛入門的小白能夠順利完成。如果正在閱讀的你也是一個剛入門的小白想用java寫一個爬取學校官網的爬蟲(大神勿噴請繞道~~~),那就很幸運了,只要你按照以下文章內容循序漸進,那一定會得到你想要的結果。(當然,在我接下來描述的項目中,是以正方教務系統來實現的,如果你的學校官網是其他的,其實掌握核心技術,也是大同小異)
?
- 前期準備:
- 項目環境:火狐瀏覽器,eclipseIDE
- 所需外庫jar包:
- commons-codec-1.3.jar
- commons-httpclient-3.1.jar
- commons-logging-1.0.4.jar
- jsouop-1.8.1.jar
注意:這幾個jar包是必不可少的,可能會有人疑惑:不是只要httpclient包就好了嘛,怎么還要其他兩個包。其實你只要知道這三個包的版本都是很老的,Apache已經不再維護更新,而且這三個包是相互依賴的,必不可缺。(由于我一開始下載的就是3.1版本,不想再改4.X的,因為里面的一些方法函數的使用是不一樣的。下載的話我已經都分享到csdn中,可以進行下載:httpclient-jar包下載),導入eclipse中的話就不用細說了,結果如下圖。
?
【注】爬蟲是什么,就不需要贅述了,如果是通過url爬取沒有權限的網頁是很簡單的,直接用Jsoup就可以完成。但是當我們在爬取一些需要登錄的網站時,我們如果還是直接訪問該url的話會重新返回到登錄首頁,所以我們就要進行模擬登錄,至于什么是模擬登錄,就是將賬號密碼驗證碼post給網站的服務器,通過記住其cookie來訪問登錄后的其他頁面(其實這是先行知識,之所以要提一嘴,是因為我在開始的時候也是一頭霧水狀態)。
- 首先分析模擬登錄過程,先打開火狐瀏覽器,打開學校教務系統登錄界面。然后按F12打開抓包界面,按正常方式進行登錄。(正方教務系統)
- 成功登錄以后接而分析登錄過程的http狀態,你會發現其中有一個狀態碼為302的POST方法和200的GET方法,然后打開這兩個提交數據,可以看見下圖。
?
其中紅框內就是請求數據界面的地址。
- 繼而分析提交數據,在抓包分析的時候會發現其中有很多的參數,其實就是在登錄的過程中瀏覽器會自動將你手動輸入的賬號密碼和驗證碼以及其自動生成的作為唯一身份驗證的cookie作為參數POST到網站的服務器,進行身份驗證,從而登錄。(至于cookie和session的區別以及POST和GET的區別下文會作解釋)參數如下圖:
在用代碼進行登錄網站時,是需要將這些表單數據作為參數提交的,但是雖然有很多參數,但是真正需要的參數只需要其中標記的四個參數:
- 學號:txtUserName
- 密碼:TextBox2
- 驗證碼:txtSecretCode
- _VIEWSTATE,該參數其實對于相同學校的官網都是一樣的,至于該參數在哪,在登錄網頁的html源碼中:
- 好了,需要提交的參數都已經了解了,那么模擬登錄就可以實現了,但是,現在問題來了,就是登錄頁面時需要提交驗證碼,那驗證碼哪里獲取呢?如下圖:
?
?
所以每一次提交信息的時候就需要get該網頁獲取驗證碼,因為對于驗證碼的自動識別比較麻煩,所以在這個項目中時將驗證碼獲取下載到本地,再人工手動輸入該驗證碼,進行登錄。
【高能:關于驗證碼的自動識別,我在下一篇博文中會做出詳細解釋】
- 在成功登錄后就可以利用cookie訪問想訪問的頁面了。
?
?
package com.yxs.Link;import org.apache.commons.httpclient.*;import org.apache.commons.httpclient.methods.*;import org.apache.commons.httpclient.cookie.*;import java.util.*;import org.jsoup.*;import org.jsoup.nodes.*;import org.jsoup.select.*;import java.io.*;/***<p>Tile:LoginTest.java<p>*<p>Description:獲取cookie和驗證碼模擬登錄教務系統<p>* @author YXS<p>* @data2018年6月3日*/public class LoginTMSystem {public static String getHTML() {String cookie1 = "";String html = "null";String txtSecretCode = "";System.out.println("請輸入學號:");String txtUserName = new Scanner(System.in).next().trim();System.out.println("請輸入密碼:");String TextBox2 = new Scanner(System.in).next().trim();String RadioButtonList1 = "";String __VIEWSTATE = "";//登錄urlString loginURL = "http://jwxt.hfnu.edu.cn/(arezfqaeu12awkft0yuoe1qg)/default2.aspx";//教務管理系統首頁urlString indexURL = "http://jwxt.hfnu.edu.cn/(arezfqaeu12awkft0yuoe1qg)/xs_main.aspx";//登錄后訪問的課程表urlString dataURL = "http://jwxt.hfnu.edu.cn/(arezfqaeu12awkft0yuoe1qg)/xskbcx.aspx";//驗證碼下載網址String checkCodeURL = "http://jwxt.hfnu.edu.cn/(arezfqaeu12awkft0yuoe1qg)/CheckCode.aspx";//創建瀏覽器對象HttpClient httpClient = new HttpClient();//先訪問驗證碼頁面,獲取驗證碼GetMethod getMethod1 = new GetMethod(checkCodeURL);try {//設置HttpClient接收CookiehttpClient.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);httpClient.executeMethod(getMethod1);//獲取訪問后的CookieCookie[] cookies1 = httpClient.getState().getCookies();StringBuffer tmpcookies1 = new StringBuffer();for(Cookie c1 : cookies1) {tmpcookies1.append(c1.toString()+";");System.out.println("訪問頁面cookies : "+c1.toString());cookie1 = tmpcookies1.toString();//cookie1 = c1.toString();}//驗證碼保存路徑File storeFile = new File("D:\\java爬蟲jar包\\verifycode\\vc.gif");InputStream is = getMethod1.getResponseBodyAsStream();FileOutputStream fos = new FileOutputStream(storeFile);byte[] b = new byte[1024];int n;while((n = is.read(b)) != -1) {fos.write(b,0,n);}is.close();fos.close();//該處代碼是用于驗證碼自動識別txtSecretCode = ImagePreProcess.getAllOrc("D:\\java爬蟲jar包\\verifycode\\vc.gif");}catch(Exception e) {e.printStackTrace();}//System.out.println("請輸入驗證碼:");//String txtSecretCode = new Scanner(System.in).next().trim();System.out.println(txtSecretCode);System.out.println("測試使用cookie1:"+cookie1);//模擬登錄,按實際服務器端要求選用Post 或 Get請求方式PostMethod postMethod = new PostMethod(loginURL);//設置相同的cookiepostMethod.setRequestHeader("cookie",cookie1);//設置登錄時需要的信息,用戶名和密碼NameValuePair[] data = {new NameValuePair("__VIEWSTATE",__VIEWSTATE),new NameValuePair("Button1",""),new NameValuePair("hidPdrs",""),new NameValuePair("hidsc",""),new NameValuePair("lbLanguage",""),new NameValuePair("RadioButtonList1",RadioButtonList1),new NameValuePair("TextBox2",TextBox2),new NameValuePair("txtSecretCode",txtSecretCode),new NameValuePair("txtUserName",txtUserName)};postMethod.setRequestBody(data);try {int statusCode = httpClient.executeMethod(postMethod);//html = postMethod.getResponseBodyAsString();System.out.println(statusCode);//重定向if(statusCode == 302) { System.out.println("模擬登錄成功!");//設置HttpClient接收Cookie,用與瀏覽器一樣的策略httpClient.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);//獲取登錄后的CookieCookie[] cookies = httpClient.getState().getCookies();StringBuffer tmpcookies = new StringBuffer();for(Cookie c : cookies) {tmpcookies.append(c.toString()+";");System.out.println("登錄頁面cookies : "+c.toString());}//System.out.println("-------------------------------InformationFrame------------------------------");//進行登錄后的操作//訪問主頁面GetMethod getMethod2 = new GetMethod(indexURL+"?xh="+txtUserName);//每次訪問需授權的網址時帶上前面的cookie作為通行證getMethod2.setRequestHeader("cookie",tmpcookies.toString());httpClient.executeMethod(getMethod2);//訪問課程表頁面GetMethod getMethod3 = new GetMethod(dataURL+"?xh="+txtUserName+"&xm=&gnmkdm=");//每次訪問需授權的網址時帶上前面的cookie作為通行證getMethod3.setRequestHeader("referer",indexURL+"?xh="+txtUserName);getMethod3.setRequestHeader("cookie",tmpcookies.toString());httpClient.executeMethod(getMethod3);html = getMethod3.getResponseBodyAsString();/*//訪問主頁面GetMethod getMethod = new GetMethod(indexURL+"?xh="+txtUserName);//每次訪問需授權的網址時帶上前面的cookie作為通行證getMethod.setRequestHeader("cookie",tmpcookies.toString());httpClient.executeMethod(getMethod);html = getMethod.getResponseBodyAsString();*/}else {System.out.println("登錄失敗");}}catch(Exception e) {e.printStackTrace();}return html;}public static void main(String[] args) {Document doc = Jsoup.parse(LoginTMSystem.getHTML());//System.out.println(doc);/*Element link1 = doc.select("span#Label3").first();String text1 = link1.text();Element link2 = doc.select("span#xhxm").first();String text2 = link2.text();System.out.println(text1+text2);*/Elements link1 = doc.select("span#Label5");Elements link2 = doc.select("span#Label6");Elements link3 = doc.select("span#Label7");Elements link4 = doc.select("span#Label8");Elements link5 = doc.select("span#Label9");String text1 = link1.text();String text2 = link2.text();String text3 = link3.text();String text4 = link4.text();String text5 = link5.text();System.out.println("-------------------------------個人信息------------------------------");System.out.println(text1);System.out.println(text2);System.out.println(text3);System.out.println(text4);System.out.println(text5);System.out.println("-------------------------------課程信息------------------------------");Elements tds1 = doc.select("td[rowspan=2]");for(int i=0;i<tds1.size();i++) {String td = tds1.get(i).text().replace(Jsoup.parse("?").text(), " ");System.out.println(td);}Elements tds2 = doc.select("td[rowspan=3]");for(int i=0;i<tds2.size();i++) {String td = tds2.get(i).text().replace(Jsoup.parse("?").text(), " ");System.out.println(td);}System.exit(0);}}
?
?
- 源碼的話,就自己解讀吧,理解核心方法的話,就很容易理解代碼的意思,而且其中也添加了許多注釋,如果還有不懂得話,可以在評論里提問。
?
??? 當你在瀏覽網站的時候,WEB 服務器會先送一些資料放在你的計算機上,Cookie會幫你在網站上所打的文字或是一些選擇,都記錄下來。當下次你再光臨同一個網站,WEB 服務器會先看看有沒有它上次留下的 Cookie 資料,有的話,就會依據Cookie里的內容來判斷使用者,送出特定的網頁內容給你。 Cookie 的使用很普遍,許多有提供個人化服務的網站,都是利用Cookie來辨認使用者,以方便送出使用者量身定做的內容,像是 Web 接口的免費 email 網站,都要用到 Cookie。
?
??? 具體來說cookie機制采用的是在客戶端保持狀態的方案,而session機制采用的是在服務器端保持狀態的方案。
同時我們也看到,由于采用服務器端保持狀態的方案在客戶端也需要保存一個標識,所以session機制可能需要借助于cookie機制來達到保存標識的目的,但實際上它還有其他選擇。
??? cookie機制。正統的cookie分發是通過擴展HTTP協議來實現的,服務器通過在HTTP的響應頭中加上一行特殊的指示以提示瀏覽器按照指示生成相應的cookie。然而純粹的客戶端腳本如JavaScript或者VBScript也可以生成cookie。而cookie的使用是由瀏覽器按照一定的原則在后臺自動發送給服務器的。瀏覽器檢查所有存儲的cookie,如果某個cookie所聲明的作用范圍大于等于將要請求的資源所在的位置,則把該cookie附在請求資源的HTTP請求頭上發送給服務器。
?
??? cookie的內容主要包括:名字,值,過期時間,路徑和域。路徑與域一起構成cookie的作用范圍。若不設置過期時間,則表示這個cookie的生命期為瀏覽器會話期間,關閉瀏覽器窗口,cookie就消失。這種生命期為瀏覽器會話期的cookie被稱為會話cookie。會話cookie一般不存儲在硬盤上而是保存在內存里,當然這種行為并不是規范規定的。若設置了過期時間,瀏覽器就會把cookie保存到硬盤上,關閉后再次打開瀏覽器,這些cookie仍然有效直到超過設定的過期時間。存儲在硬盤上的cookie可以在不同的瀏覽器進程間共享,比如兩個IE窗口。而對于保存在內存里的cookie,不同的瀏覽器有不同的處理方式
?
??? session機制。session機制是一種服務器端的機制,服務器使用一種類似于散列表的結構(也可能就是使用散列表)來保存信息。當程序需要為某個客戶端的請求創建一個session時,服務器首先檢查這個客戶端的請求里是否已包含了一個session標識(稱為sessionid),如果已包含則說明以前已經為此客戶端創建過session,服務器就按照sessionid把這個session檢索出來使用(檢索不到,會新建一個),如果客戶端請求不包含sessionid,則為此客戶端創建一個session并且生成一個與此session相關聯的session id,sessionid的值應該是一個既不會重復,又不容易被找到規律以仿造的字符串,這個session id將被在本次響應中返回給客戶端保存。保存這個sessionid的方式可以采用cookie,這樣在交互過程中瀏覽器可以自動的按照規則把這個標識發送給服務器。一般這個cookie的名字都是類似于SEEESIONID。但cookie可以被人為的禁止,則必須有其他機制以便在cookie被禁止時仍然能夠把sessionid傳遞回服務器。
??? 經常被使用的一種技術叫做URL重寫,就是把sessionid直接附加在URL路徑的后面。還有一種技術叫做表單隱藏字段。就是服務器會自動修改表單,添加一個隱藏字段,以便在表單提交時能夠把sessionid傳遞回服務器。比如:
<form name="testform"action="/xxx">
<input type="hidden"name="jsessionid"value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
<input type="text">
</form>
實際上這種技術可以簡單的用對action應用URL重寫來代替。
cookie 和session 的區別:
1、cookie數據存放在客戶的瀏覽器上,session數據放在服務器上。
2、cookie不是很安全,別人可以分析存放在本地的COOKIE并進行COOKIE操作,考慮到安全應當使用session。
3、session會在一定時間內保存在服務器上。當訪問增多,會比較占用你服務器的性能,考慮到減輕服務器性能方面,應當使用COOKIE。
4、單個cookie保存的數據不能超過4K,很多瀏覽器都限制一個站點最多保存20個cookie。
- 從安全性看get<post,get提交數據會在瀏覽器的地址欄顯示
- 從提交的內容大小來看get<post,get提交的數據不能大于2k,而post提交的數據上理論不受限制,實際編程中建議不要大于64k
- 從請求響應速度來看get>post,get要求服務器立即處理請求,而post請求可能形成一個隊列請求。
?
【坑1】
- 在前期停滯了兩天,就是關于那個登錄界面的驗證碼和你訪問驗證碼網頁獲得的驗證碼是否一致的問題,我當時一直搞不懂如何保持相同,也搜了很多但是都是沒有說到點子上或者很多教程都是沒有在該點上側重注意的,所以這一點算是坑死我。下圖是當時搜的一些討論。后來還是經過學長的指點才算是醍醐灌頂。
- 先簡單說說我當時的思考思路:由于我在POST參數時,需要將賬號密碼驗證碼和提交,這時是POST到loginURL,但是我有驗證碼的前提是要GET下checkCodeURL下載驗證碼,可是我怎么保證兩者的驗證碼就是同一驗證碼呢?
- 所以我想先GETcheckCodeURL獲取驗證碼,同時獲取該網頁的cookie,然后再用相同的cookie作為參數POST到loginURL,這樣的意思就是保證讓服務器知道我兩次訪問的是同一用戶,以保證兩個驗證碼保持一致,但是結果,當然毫無起色!!!!!那么問題出在那了?
- 看了這么多圖片,你應該能夠發現每個URL中都有和普通URL不同之處,如下圖,URL中有一長串字符編碼,【重點】其實這個就是對每個網頁的一個唯一標識碼,就是當不同次訪問網頁是服務器都會生成一個標識碼,只有當這個標識碼保持一致時,服務器才會認定兩個驗證碼是保持一致的。
?
【坑2】
- 其實解決完坑1后,你就會發現可以成功登錄了,但是這時當你想POST課程表頁面時,會出錯!WTF ?!
- 這時也是有三個參數要提交,和前面的類似,但是關鍵關鍵之處:一定要加該句,用于告訴該POST的網頁你從哪里來。
?
總結
以上是生活随笔為你收集整理的基于HttpClient的正方教务系统模拟登录(带验证码)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。