從爬取湖北某高校hub教務系統課表淺談Java信息抓取的實現 —— import java.*;

原創文章與源碼,若是轉載請註明來源。  javascript

開發環境:Myeclipse,依賴包:apache-httpclientJsoupbase64php

 

1、概述html

 

 

 

整個系統用Java開發。咱們如今要作的是相似於超級課程表、課程格子之類的功能:輸入一個學生的教務系統帳號、密碼,獲得Ta的課程表信息。點擊進入課表查詢,咱們發現了這樣的頁面:java

這就是咱們須要的結果。其實思路很簡單,用java訪問這個連接,拿到Html字符串,而後解析連接等須要的數據。web

這個頁面的URL是http://s.hub.hust.edu.cn/aam/report/scheduleQuery.jsp算法

所以,咱們發送HTTP請求GET http://s.hub.hust.edu.cn/aam/report/scheduleQuery.jsp,這樣就能夠等到課表的內容了。可是,這個頁面必須是在登陸以後才能訪問的,若是直接發送GET請求的話,系統會認爲你沒有登陸,因此會拒絕你的請求(跳轉到登陸頁面),因此,在發送GET請求以前,必須實現模擬登陸。spring

2、JAVA中GET/POST請求的實現數據庫

 

在進行模擬登陸以前,咱們須要瞭解一些基本知識。apache

首先請看關於HTTP請求的基礎知識:http://www.cnblogs.com/yin-jingyu/archive/2011/08/01/2123548.html編程

在java中,實現執行http請求有多種方式,好比使用urlconnection等等,不過在這裏咱們使用apache-httpclient。HttpClient 是 Apache Jakarta Common 下的子項目,能夠用來提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,而且它支持 HTTP 協議最新的版本和建議。

 1 //1. 首先建立一個CookieStore用於存儲Cookie數據
 2 
 3 CookieStore cookieStore = new BasicCookieStore();
 4 
 5 //2.建立httpclient,並關聯CookieStore
 6  
 7 DefaultHttpClient client = new DefaultHttpClient();
 8 client.setCookieStore(cookieStore);
 9 
10 client.getParams().setParameter(CookieSpecPNames.DATE_PATTERNS, Arrays.asList("EEE, dd MMM yyyy HH:mm:ss z")); //該代碼用於設置cookie中的expires時間日期格式。添加該代碼是由於華科網站使用的cookie日期格式不是標準格式。

建立GET請求:

1 HttpGet get = new HttpGet("http://xxxx");
2 get.setHeader("xxx","xxx");
3 get.setHeader("xxxx","xxxx");
4 get.setHeader("Cookie","cookie");
5 HttpResponse response = client.execute(get);
6 get.releaseConnection();

建立POST請求:

        HttpPost post = new HttpPost("http://xxxx");
        post.setHeader("xxx","xxxx");
        post.setHeader("xxxx","xxxx");
        post.setHeader("Cookie","cookie");
        
        //對post請求發送參數
        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        nvps.add(new BasicNameValuePair("username", "111"));
        nvps.add(new BasicNameValuePair("password", "xxx"));
        post.setEntity(new UrlEncodedFormEntity(nvps,"utf-8"));
     HttpResponse response = client.execute(post);

從CookieStore獲得Cookie字符串

1         StringBuilder stringBuilder = new StringBuilder();
2         for(Cookie cookie:cookieStore.getCookies()){
3             String key =cookie.getName();
4             String value = cookie.getValue();
5             stringBuilder.append(key).append("=").append(value).append(";");
6         }
7         
8         return stringBuilder.toString();

從HttpResponse對象中獲取執行的結果(輸入流)

1         InputStream inputStream = response.getEntity().getContent();
2         //獲取結果的輸入流

從輸入流中獲取字符串,能夠用以下的函數(注意編碼問題)

1     public static String in2Str(InputStream in) throws IOException{
2         BufferedReader rd = new BufferedReader(new InputStreamReader(in,"utf-8"));
3         String line = null;
4         StringBuilder sb = new StringBuilder();
5         while ((line=rd.readLine())!=null) {
6             sb.append(line).append("\r\n");
7         }
8         return sb.toString();
9     }

Jsoup解析

參考資料:http://www.open-open.com/jsoup/

 

以上幾段程序代碼就是咱們程序工做的核心了,在個人源碼中,對這些代碼進行了封裝,你能夠輕鬆找到它們(在spider包中)。

 

3、模擬登陸的實現

 

通常地,在java web中,登陸能夠由相似於以下的代碼實現:

前臺html的代碼以下:

1 <form action="/login.action" method="post" >
2     <label for="username">用戶名</label>
3     <input id="username" name=「username" type="text" />
4     <label for="password">密碼</label>
5     <input id="password" name=「password" type="password" />    
6     <input type="submit" value="登陸" />
7 
8 
9 </form>

後臺action以下(spring mvc):

 1 @RequestMapping("/login.action")
 2     public String loginSubmit(HttpServletRequest request,HttpServletResponse response,
 3   @RequestParam("username") String username,@RequestParam("password") String password) {
 4    
5
6     if(username==null||password==null){ 7 request.setAttribute("msg", "您的輸入有誤!"); 8 return "/login"; 9     } 10     if(username.equals("")||password.equals("")){ 11 request.setAttribute("msg", "您的輸入有誤!"); 12 return "/login"; 13    } 14   User user = userDao.getUser(username, password); 15   if(user==null){ 16        //TODO 登陸失敗 17        return "xxx"; 18   }else{ 19       request.getSession().setAttribute("loginUser",user); //保存登陸後的用戶到session 20       //TODO 登陸成功 21        return "xxx"; 22   } 23    24 }

其實登陸也就是發送POST請求,服務器接收到POST請求(Request)後,對其處理(查詢數據庫等),返回Response。

其中最關鍵的與身份驗證有關的操做就是request.getSession().setAttribute("loginUser",user) 了。將登陸後的用戶保存到session中,這樣,在訪問其餘須要身份驗證的頁面時,服務器只須要判斷session中是否有該用戶,若是有就表示身份驗證經過,若是沒有則表示身份驗證失敗。而java中對於session的實現是依賴於cookie中的jsessionid屬性的(參考文檔),若是模擬出登陸請求後(也就是模擬一個POST請求),獲得cookie(也就是獲得jsessionid),下次請求時將cookie發送給服務器以代表身份,不就能夠訪問帶有權限的URL了麼?

 

首先咱們須要下載webscrab,這個軟件有多強大這裏就不細說了,你們能夠自行百度下載地址。下載後是.jar格式,怎麼運行不用我多說了吧。關於webscrab的使用見webscrab.pdf

 

(webscrab的核心設置)

 

1.攔截登陸時的POST請求:(若是不會請參考使用說明或者百度webscrab的使用)

這裏咱們須要這兩種信息:Parsed和URLEncoded,其中,Parsed是POST請求的URL和Header,而URLEncoded則是該請求發送的參數。

咱們先看Parsed部分,Parsed部分是由Method、URL和響應頭(以<Header,Value>表示的Map型結構)組成。Method表示該請求是POST請求仍是GET請求;響應頭對應了HttpGet/HttpPost類中的setHeader方法,大多數Header不是必須的,可是在請求時,最好加上相同的Header,以避免出現一些問題。例如:若是沒有Host(該值表示域名,例如url是http://www.abc.com/login.action,則該值就是www.abc.com)或者Referer頭(表示發起請求時的頁面,告訴服務器我是從哪裏過來的,好比是http://www.abc.com/login.html),在某些狀況下可能會出現404錯誤。【這多是因爲服務器設置了防盜鏈機制】

所以,最好的處理是將攔截到的Header,都添加到HttpGet/HttpPost中。

或者以一個HashMap的方式存儲:(spider.tools.hub.HubEventAdapter和SHubEventAdapter)

1         HashMap<String, String > map = new HashMap<>();
2         map.put("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
3         map.put("Referer", "http://hub.hust.edu.cn/index.jsp");
4         map.put("Accept-Language","zh-CN,zh;q=0.8");
5         map.put("User-Agent", useragent);
6         map.put("Accept-Encoding", "gzip, deflate");
7         map.put("Host", "hub.hust.edu.cn");
8         map.put("Proxy-Connection", "Keep-Alive");
9         map.put("Pragma", "no-cache");

遍歷它們,調用setHeader方法。

 

下面咱們再來看URLEncoded部分,該部分表示POST請求發送給服務器的數據。咱們發現,其中有三項數據username、password、ln。

咱們發現,這裏的password值並非咱們剛剛輸入的密碼,而彷佛是一種加密以後的結果,查看http://hub.hust.edu.cn/index.jsp的源代碼,發現以下代碼:(第210行)

                    var password = $("input[name='password']").val();
                    if(password==""){
                        alert("請輸入用戶密碼(Password)");
                        $("input[name='password']").focus();
                        return false;
                    }
                    
                    
                    
                    $("input[name='password']").val($.base64.encode(password)); //咱們要找的東西在這裏!!!

很明顯,$.base64,這是base64加密,因此在咱們發送POST請求以前,應該對密碼進行一次base64加密後再發送。(能夠根據密碼長度判斷是什麼加密類型,通常都是base64加密,32位通常是MD5加密,再長一些則多是AES加密,若是結果很是長則極可能是RSA加密。)

而ln值,你能夠嘗試反覆刷新頁面,反覆提交、攔截,會發現每次ln值都會改變,對於這樣每次會改變的值,咱們採起這樣的方式:

GET /index.jsp -> cookie、ln  - >POST /login.action 

首先對首頁執行GET方法,獲取首頁的HTML內容,並保存cookie。、

接下來用Jsoup解析首頁的html內容,獲得ln值。

最後將ln值與cookie,加上用戶輸入的用戶名、密碼一塊兒POST到/login.action 。

3.中轉登陸

在發送POST請求後,使用(二)中提供的in2Str方法,獲得返回結果,竟然發現結果以下:

 1 <body>
 2     <form action="" method="post" name="form1">
 3         <input type="hidden" id="usertype" name="usertype" value="xs">
 4         <input type="hidden" id="username" name="username" value="U2013">
 5         <input type="hidden" id="password" name="password" value="1061d0c  (這裏爲了用戶隱私我沒有顯示)
 6 269c">
 7         <input type="hidden" id="url" name="url" value="http://s.hub.hust.edu.cn/">
 8         <input type="hidden" name="key1" value="367265"/>
 9         <input type="hidden" name="key2" value="a261ab7e0cecb430651868727cd3fb35"/>
10         <input type="hidden" name="F_App" value="From kslgin. App:app61.dc.hust.edu.cn
11 |app614|IP:10.10.10.247"/>
12     </form>
13     <script type="text/javascript">
14     var url = document.getElementById("url").value;
15     document.form1.action=url+'hublogin.action';
16     document.form1.submit();
17     </script>
18 </body>

原來這就是華科中轉登陸的機制啊。仍是同樣的發送POST請求

POST http://s.hub.hust.edu.cn/hublogin.action

usertype,username,password,url,key1,key2,F_App。

注意:此時的域名已經改成http://s.hub.hust.edu.cn/了,那麼Header中的Host和Refer值最好也改成http://s.hub.hust.edu.cn/。

4.返回

使用下面代碼獲取POST執行後的整型返回值:

int code = response.getStatusLine().getStatusCode();

若是code=302則登陸成功,不然登陸失敗。(302也就是表示登陸已經成功,能夠跳轉到其餘頁面了。)

 

4、課表的獲取

在第三部登陸成功以後,咱們發現GET  http://s.hub.hust.edu.cn/aam/report/scheduleQuery.jsp 彷佛不包含我想要的課表信息,因而繼續使用webscrab。

點擊「課表查詢」,繼續攔截請求,經過幾回攔截,發現有一個請求應該包含我須要的課表信息。

所以,仍是使用跟以前相似的方法,發送POST請求

POST http://s.hub.hust.edu.cn:80/aam/score/CourseInquiry_ido.action

start = 2016-02-29

end = 2016-04-11

別忘記帶上第三步(登陸後)的Cookie!

最後獲得的結果以下:

噹噹~~當————

 

點擊下一月,URLEncoded變成了:

這樣的日期彷佛比較亂啊,

若是將start設置爲2016-03-01,end設置爲2016-03-31,獲取的就是3月的課表。

 

至此,華科大教務系統課表爬取完成!

 

5、總結

 

個人代碼的編程思路:(用抽象語言表述)

 1 //總體代碼用抽象Java語言表示,這些代碼只是表示設計思路。不能運行
 2 
 3 Header header1 = {"refer","http://hub.hust.edu.cn/index.jsp","host":"hub.hust.edu.cn"}; //響應頭header1
 4 Header header2 = {"refer","http://s.hub.hust.edu.cn/index.jsp","host","s.hub.hust.edu.cn"}; //響應頭header2
 5 
 6 Get get = new Get ("http://hub.hust.edu.cn/index.jsp").header(header1);   //進入首頁
 7 Response res1 = get.execute();
 8 String content1 = res1.getContent();  //獲取index.jsp的html代碼
 9 String ln = getln(Jsoup.parse(content1));  //使用jsoup解析index.jsp的html代碼,從中獲取出ln(input hidden name='ln'的value)
10 
11 Post post = new Post("http://hub.hust.edu.cn/hubulogin.action").header(header1);   //準備模擬登陸的,POST提交
12 
13 //添加post數據
14 post.add("username","123456789"); 
15 post.add("password",base64encode("mypassword"));
16 post.add("ln",ln)
17 
18 Response res2 = post.execute();  //執行post請求
19 
20 Post post2 = new Post("http://s.hub.hust.edu.cn/hublogin.action").header(header2); //中轉登陸,注意header的變化
21 Document dform = Jsoup.parse(res2.getContent());  //獲得返回的動態表單內容
22 post.add("usertype",getUserType(d));
23 post.add("username",getUserName(d));
24 post.add("password",getPassword(d));
25 post.add("url",getURL(d));
26 post.add("key1",getKey1(d));
27 post.add("key2",getKey2(d));
28 post.add("F_App",getFApp(d));
29 
30 Response res3 = post2.execute();
31 
32 if(res3.getStatusLine().getStatusCode()==302){
33     syso("登陸成功");
34 }else{
35     syso("登陸失敗");
36     return;
37 }
38 
39 
40 Post kbPost = new Post("http://s.hub.hust.edu.cn:80/aam/score/CourseInquiry_ido.action").header(header2); //獲取課表的post請求
41 kbPost.add("start","2016-03-01");
42 kbPost.add("end","2016-03-30");
43 Response res4 = kbPost.execute();
44 if(res4.getStatusLine().getStatusCode()==200){
45     syso(res4.getContent());
46 }else{
47     syso("服務器異常!");
48 }

 

 

 

擴展:

你能夠直接在個人基礎上擴展,適用於其餘學校的「課程格子」。

你能夠選擇繼承AbstractTask類來表示一項POST/GET請求任務,用getEvent方法來表示該任務的具體內容,最好是對SpiderTaskEvent使用適配器模式。

示例代碼以下:(這是基於另外一個學校的教務系統實現)

  

 1 public abstract class JwxtEventAdapter implements SpiderTaskEvent{
 2     
 3     private static final String useragent = "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko";
 4     List<StringHeader> headers;
 5     
 6     private Cookies cookies;
 7     public JwxtEventAdapter(Cookies cookies){
 8         HashMap<String, String> map = new HashMap<>();
 9         map.put("Accept","text/html, application/xhtml+xml, image/jxr, */*");
10         map.put("Referer", "http://jwxt.hubu.edu.cn/");
11         map.put("Accept-Language","zh-Hans-CN,zh-Hans;q=0.8,en-GB;q=0.5,en;q=0.3");
12         map.put("User-Agent", useragent);
13         map.put("Accept-Encoding", "gzip, deflate");
14         map.put("Host", "jwxt.hubu.edu.cn");
15         map.put("Proxy-Connection", "Keep-Alive");
16         map.put("Pragma", "no-cache");
17         
18         headers = StringHeader.build(map);
19         this.cookies = cookies;
20     }
21     public JwxtEventAdapter(){
22         this(null);
23     }
24     
25     @Override
26     public void beforeExecute(SpiderRequest request) throws IOException {
27         request.setTimeout(20000);
28         request.setHeaders(headers);
29         if(cookies!=null){
30             request.setCookie(cookies);
31         }
32     }
33     
34     @Override
35     public void afterExecute(SpiderRequest request, SpiderResponse response)
36             throws IOException {
37         
38         
39     }
40 
41 }
 1 public class JwxtRandomTask extends AbstractTask {
 2 
 3     private String random;
 4     
 5     private Image image;
 6 
 7     public Image getImage(){
 8         return image;
 9     }
10 
11     /**
12      * @param client
13      */
14     public JwxtRandomTask(HttpClient client) {
15         super(client);
16         
17     }
18 
19     public String getRandom() {
20         return random;
21     }
22 
23     @Override
24     public Method getMethod() {
25 
26         return Method.GET;
27     }
28 
29     @Override
30     public String getURL() {
31 
32         return "http://jwxt.hubu.edu.cn/verifycode.servlet";
33     }
34     
35     
36 
37     @Override
38     public SpiderTaskEvent getEvent() {
39 
40         return new JwxtEventAdapter() {
41 
42             @Override
43             public void afterExecute(SpiderRequest request,
44                     SpiderResponse response) throws IOException {
45                 image = ImageIO.read(response.getContentStream());
46 
47             }
48         };
49     }

 

  

 

我在寫這個程序的時候,確實遇到了一些麻煩,就好比本文提到的404的問題;以及我多是有點急躁吧,一開始沒有注意到其實這個登陸action是有一次中轉的,致使後面的GET操做都被系統提示爲非法操做。

確實作這個讓本身感慨萬千,大學幾年來一直難以踏踏實實的作一些事情,太浮躁,C語言、算法、Java等等都是不精,只學了一點皮毛。一個大三學生班門弄斧,滿紙荒唐言,若有錯誤還請各位大神批評和指出,很是感謝!最後感謝一下提供帳號的同窗d=====( ̄▽ ̄*)b。

但願之後能越走越遠!import java.*;

ps.個人源碼下載地址:下載1    下載2

相關文章
相關標籤/搜索