用HttpClient抓取人人網高校數據庫(省,高校,院系三級級聯)--更新1

    更新備註:將src文件改爲了一個完整的項目,解壓後能夠直接導入到Eclipse中去,省去你們配置(項目亂碼請改項目屬性爲GBK)。另外,若是你要登錄人人網 的話,須要申請一我的人網帳號。這裏提供公用的:\

lei.d0809@gmail.comhtml

java123456java

代碼請見: http://www.oschina.net/code/snippet_96412_1983ajax

http://dl.iteye.com/topics/download/30a7fe9e-2c52-3620-b99e-f835a2ddad2b正則表達式

請自行修改RenRenNotify.java 對應的東西。sql


       首先文章有點長,須要點耐心。這裏我是一步一步的作的。。。。比較的細,若是你是代碼達人,那你就直接下載代碼吧。數據庫

      有人說圖片看不清,我抱歉,第一次咱的圖片不完美,你把圖片在瀏覽器上拖動到新窗口,就能夠看到你大圖了。apache

 

 


       需求來源,最近學校的課程項目須要一個省,高校,院系的三級級聯的東西,這下麻煩了。全國那麼多的高校,並且每個高校的院 系設置又不同,咱們小組只有六我的,並且技術都不咋地,要統計那麼多的數據,咱們估計這學期就別想完成這個項目了。可是咱們知道人人網,開心網,騰訊微 博上都要高校的數據庫,因而想法就產生了:json

     1.要麼咱拼人品讓他們的技術人員給咱們他們的數據庫,想法是好的,可是人家不願呀瀏覽器

     2.要麼咱經過某種手段獲取他們的數據服務器

今天,咱選擇第二種。用到工具備:

EditPlus:小巧好用的文本編輯器,是超越的文本編輯器,不解釋,用了就知道

Apanta:這個強烈推薦,用它來寫Html,Javascript,Css感受很是好,並且支持各類各樣的Javascript的庫,如:

               Jquery,可是我想把他集成到MyEclipse上去,出了一點問題,遺憾,弄的我只能同時開啓兩個。

HttpAnalyzer:這個是用來抓包用的,不管什麼包通通抓,不過只能抓Http協議的包,當年傻,分析飛信協議的時候,

                用這個抓,結果只抓了一點東西。若是你想抓取更底層的推薦一個:WireShark,免費的好用的。

MyEclipse:這個很少說了,弄過J2EE的應該都知道的。

另外就是第三Jar包了,HttpClient 4.01 請到:http://hc.apache.org/downloads.cgi 下載,只要是4版本上的都應該能夠,若是是3.1版本的估計你要從新寫一些代碼,由於4較3仍是有很大的改進的。

 

      通常來講,一個網站對訪問它內部的東西須要權限的驗證的,好比你下載某個網站的東西,他會提示說 只有會員才能夠下載,因而乎,這裏存在一個session,保存了你的登錄信息也就是你的訪問網站內部資源的權限了。人人網估計也不是省油的燈(這裏有問 題,後面解釋),因而咱們應該登錄它才能得到訪問它內部資源的權限。那麼咱們首先來抓包分析應該怎麼用登錄,因而HttpAnalyzer閃亮登場.

打開HttpAnalyzer,讓他開始工做,咱們打開瀏覽器,輸入renren.com。第一次咱先不急着登錄。咱們隨便輸入一個帳號密碼看看:

 

 

 

      咱們看到當你輸入用戶名密碼後就將你輸入的東西post到:http://www.renren.com/PLogin.do,

其中PostData有四個:email,password,origURL,domain。至於後面的數據是咱們剛剛在登錄頁面上填寫的數據。

咱們再來看看它登錄頁面的源代碼:

 

 

      注意我紅色標註的地方:咱們注意到,除了咱們剛剛在上面發送的數據還有其餘的隱藏發送的的東西:例如:origURL等等,這裏他們 是<input type="hidden" />,應該說在form裏面的input都應該發送過去,可是這裏他只發送了四個。

既然postdata只有那麼四個參數,那咱們就姑且只用那個四個東西好了。

因此咱們用HttpClient構造請求的時候,就應該將這四個參數的給附帶進去,部分代碼以下:

// 將要發送的數據封包  
      List<NameValuePair> params = new ArrayList<NameValuePair>();  
      params.add(new BasicNameValuePair("email", this.email));  
      params.add(new BasicNameValuePair("password", this.password));  
      params.add(new BasicNameValuePair("origURL", origURL));  
      params.add(new BasicNameValuePair("domain", domain));

 

接下來咱們來完整登錄一次:

當輸入正確的用戶名密碼,點擊登錄,咱們又得到什麼樣的東西呢?參見以下:

 

 

返回的內容意思大概是 地址轉變了要進行跳轉,並且返回的相應頭是 302,文件修改了。再看一下 返回的消息頭:

 

 

       有一個Location,應該是要咱們跳轉的地址。這樣咱們應該能夠訪問人人網的任意鏈接資源了。

 

登陸過程的完整代碼(包含讀嗅探指定資源的連接):

 

import java.io.IOException;  
    import java.io.UnsupportedEncodingException;  
    import java.util.ArrayList;  
    import java.util.List;  
      
    import org.apache.http.HttpResponse;  
    import org.apache.http.NameValuePair;  
    import org.apache.http.client.ClientProtocolException;  
    import org.apache.http.client.ResponseHandler;  
    import org.apache.http.client.entity.UrlEncodedFormEntity;  
    import org.apache.http.client.methods.HttpGet;  
    import org.apache.http.client.methods.HttpPost;  
    import org.apache.http.impl.client.BasicResponseHandler;  
    import org.apache.http.impl.client.DefaultHttpClient;  
    import org.apache.http.message.BasicNameValuePair;  
    import org.apache.http.protocol.HTTP;  
      
    /** 
     *  
     *  
     * Author : Saitkey < lei_d@foxmail.com > 
     */  
    public class RenRenNotify {  
        private static HttpResponse response;  
        private static DefaultHttpClient httpClient;  
      
        public RenRenNotify(String userName, String password) {  
            this.httpClient = new DefaultHttpClient();  
            String loginForm = "http://www.renren.com/PLogin.do";  
            String origURL = "http://www.renren.com/Home.do";  
            String domain = "renren.com";  
            // 在首頁表單上是隱藏的 抓包後分析,並無發送到服務器  
            // String autoLogin = "true";  
            // 構造一個POST請求,利用Httclient提供的包  
            HttpPost httpPost = new HttpPost(loginForm);  
            // 將要發送的數據封包  
            List<NameValuePair> params = new ArrayList<NameValuePair>();  
            params.add(new BasicNameValuePair("email", userName));  
            params.add(new BasicNameValuePair("password", password));  
            params.add(new BasicNameValuePair("origURL", origURL));  
            params.add(new BasicNameValuePair("domain", domain));  
      
            // 封包添加到Post請求  
            try {  
                httpPost.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));  
            } catch (UnsupportedEncodingException e1) {  
                // TODO Auto-generated catch block  
                e1.printStackTrace();  
            }  
            // 將 get 和post 方法包含到一個函數裏面去,這裏就是登錄過程了。  
            response = postMethod(httpPost);  
            /* 
             * 有跳轉 System.out.println(response.getStatusLine());//返回302 
             * Header[]headers=response.getAllHeaders(); for (int i = 0; i < 
             * headers.length; i++) { Header header = headers[i]; 
             * System.out.println(header.getName()+": "+header.getValue()); } 
             */  
            // 讀取跳轉的地址  
            // String redirectUrl = response.getFirstHeader("Location").getValue();  
            // 查看一下跳轉事後,都出現哪些內容.  
            // response=getMethod(redirectUrl);//函數見後面  
            // System.out.println(response.getStatusLine()); // HTTP/1.1 200 OK  
      
            // 讀取一下主頁都有什麼內容 已經登錄進去  
            // System.out.println(readHtml("http://www.renren.com/home"));  
        }  
      
        // 嗅探指定頁面的代碼  
        public String notify(String url) {  
            HttpGet get = new HttpGet(url);  
            ResponseHandler<String> responseHandler = new BasicResponseHandler();  
            String txt = null;  
            try {  
                txt = httpClient.execute(get, responseHandler);  
            } catch (ClientProtocolException e) {  
                e.printStackTrace();  
            } catch (IOException e) {  
                e.printStackTrace();  
            } finally {  
                get.abort();  
            }  
            return txt;  
        }  
      
        // 用post方法向服務器請求 並得到響應,由於post方法要封裝參數,所以在函數外部封裝好傳參  
        public HttpResponse postMethod(HttpPost post) {  
            HttpResponse resp = null;  
            try {  
                resp = httpClient.execute(post);  
            } catch (ClientProtocolException e) {  
                e.printStackTrace();  
            } catch (IOException e) {  
                e.printStackTrace();  
            } finally {  
                post.abort();  
            }  
            return resp;  
        }  
      
        // 用get方法向服務器請求 並得到響應  
        public HttpResponse getMethod(String url) {  
            HttpGet get = new HttpGet(url);  
            HttpResponse resp = null;  
            try {  
                resp = httpClient.execute(get);  
            } catch (ClientProtocolException e) {  
                e.printStackTrace();  
            } catch (IOException e) {  
                e.printStackTrace();  
            } finally {  
                get.abort();  
            }  
            return resp;  
        }  
      
        public static void main(String[] args) {  
            RenRenNotify notify = new RenRenNotify("[你的用戶名]",  
                    "[你的密碼]");  
            System.out.println(notify  
                    .notify("http://www.renren.com/home"));  
        }  
      
    }

 

        好了,如今登陸了。咱們去修改本身的教育信息吧,首先天然是進入相應的頁面:

 

       當咱們進入了修改教育信息的時候,咱們發現HttpAnalyzer裏面多了以下內容:

 

 

 

   注意紅色的內容。這裏應該是全部高校的信息。體積也達到了402kb,看一下里面的內容:

 

 

 

        這個裏面有個奇怪的東西:\u4e2d\u56fd 這個是 「中國」的意思,通過轉碼了。用JavaScript 直接 alert('u4e2d\u56fd '),就明瞭了。

        對於一長串的字符,能夠用下面的代碼進行回來(code是源):

StringBuffer sb = new StringBuffer(code);  
    int pos;  
    while ((pos = sb.indexOf("\\u")) > -1) {  
        String tmp = sb.substring(pos, pos + 6);  
        sb.replace(pos, pos + 6, Character.toString((char) Integer  
                .parseInt(tmp.substring(2), 16)));  
    }  
    code = sb.toString();

     

     接下來,咱們選擇一個高校看看,HttpAnalyzer裏面出現以下的信息:

     

     

     

     

    再來一下:

     

     

     

     

           因此經過上面兩次抓取,咱們應該得出一個例子,那就是:咱們選擇好了一個大學,就會相應的得出他的ID,而後這時候會想服務器發送一個請求查詢:http://www.renren.com/GetDep.do?id=13003 ,其中id後面的即是高校的代號了。而後返回的是一串html代碼,以下:

     

     

     

             這裏一樣是奇怪的一串數字,這種也是Unicode,不過是十進制的,並且在編碼的先後分別加上「&#」和「;」就能夠造成Html實體字符,能夠在網頁上直接顯示。

     

     

    對於以上的代碼,咱們也參照上面寫一個轉換的代碼:以下:

      

    StringBuffer sb=new StringBuffer(code);  
    int pos;  
    while ((pos=sb.indexOf("&#"))>-1) {  
        String tmp=sb.substring(pos+2, pos+7);  
        sb.replace(pos, pos+8, Character.toString((char)Integer.parseInt(tmp,10)));  
    }  
    code=sb.toString();

     

            寫到這裏,咱們的工做也作了一大半了。因而乎,我這裏不得不跟你們陳清一個事實,得到

    http://s.xnimg.cn/a13819/allunivlist.js

    http://www.renren.com/GetDep.do?id=13003

    的頁面代碼,人人網是沒有設置 session的權限認證的。直接能夠讀不信你能夠點擊上面的兩個地址,你就發現,原來能夠直接讀取的。

     

           也就是說。咱們能夠另闢路徑,不用經過HttpClient去登錄一下在取得數據,這一點很很差意思。我剛開始沒有意識到。不過,這裏你也仍是學會了一種登錄一個服務器的辦法,說不定之後你會用到呢。

     

     

    好了,下面咱們開始另外一種方法。

           首先,咱們對獲取http://s.xnimg.cn/a13819/allunivlist.js的數據進行分析一下:

    var allUnivList = [{"id":"00","univs":"","name":"\u4e2d\u56fd","provs":[{............."country_id":0,"name":"\u53f0\u6e7e"}]},{"id":"01","univs"...................

    這樣的數據類型。我想作過ajax的都知道是json類型的。 可是這裏我要用Java的正則表達式進行解析。

     

    首先分析數據結構:

    [{國家:[{省市區[{高校S}],......}],....},....] 大概就是這樣的結構 其中....表示可能有多個 同級機構。如 安徽省呵北京市, 而在北京市下有 清華大學和北京大學 是同級的。以此類推啦。

     

    我只須要中國的的大學,因此我首先選出中國這塊的數據:用到的正則表達式是:"\"provs\":(.*?)]}"

    這裏主要對比 在臺灣省結束的時候,有]}標誌,並且在前面並無出現,並且用非貪婪模式去批判就能保證是中國的高校了。如圖

     

     

    取得了中國部分,接下來對中國的省市區進行解析了,一樣,咱們看到:

    [{"id":"00",............"country_id":0,"name":"..........."},這樣的結構

     

    因此對每個省咱們能夠分析到以下的正則表達式:id\":(.*?),\"univs\":(.*?),\"country_id\":0,\"name\":\"(.*?)\"}

           而後對 中國這部分進行一個循環,就能夠獲得中國全部的省市區了,一樣咱們對每個省市,要對他們包含的高校進行選擇:

     

           咱們很容易就能夠看到高校的 結構應該是:{"id":1001,"name":"\u6e05\u534e\u5927\u5b66"} 相似,那麼正則表達式應該是:"id\":(.*?),\"name\":\"(.*?)\"";

     

           對於每個高校,咱們能夠相似於省市那樣處理,用循環匹配,就能夠獲得這個省市的因此高校。可是對於每個高校。咱們要還須要得到他的院系信息。前文跟你們分分析了,院系信息是經過http://www.renren.com/GetDep.do?id=xxxx來動態獲取(xxx表明高校的編號),那麼咱們在抓取高校的時候,順帶也將他們的院系信息獲取了。

    寫了這麼多,咱直接上代碼:

          你也能夠選擇下載下面的代碼。裏面有一些必要的文件已經jar包,須要本身配置一下。若是不會,請留言吧,我爭取從新打包再上傳上來。

     

    import java.io.File;  
        import java.io.IOException;  
        import java.io.PrintStream;  
        import java.util.regex.Matcher;  
        import java.util.regex.Pattern;  
          
        import org.apache.http.client.ClientProtocolException;  
        import org.apache.http.client.HttpClient;  
        import org.apache.http.client.ResponseHandler;  
        import org.apache.http.client.methods.HttpGet;  
        import org.apache.http.impl.client.BasicResponseHandler;  
        import org.apache.http.impl.client.DefaultHttpClient;  
          
        /** 
         *  
         *  
         * Author : Saitkey < lei_d@foxmail.com > 
         */  
        public class GenerateSQL {  
            // 構建省的sql文件  
            private File province = new File("provice.sql");  
            // 構建高校的sql文件  
            private File college = new File("college.sql");  
            // 構建院系的sql文件  
            private File department = new File("department.sql");  
          
            GenerateSQL() throws ClientProtocolException, IOException {  
                HttpClient client = new DefaultHttpClient();  
                ResponseHandler<String> responseHandler = new BasicResponseHandler();  
                String depUrl = "http://www.renren.com/GetDep.do?id=";  
                String allunivs = "http://s.xnimg.cn/a13819/allunivlist.js";  
                HttpGet get = new HttpGet(allunivs);  
                System.out.println("讀取高校信息...");  
                StringBuffer sb = new StringBuffer(client.execute(get, responseHandler));  
                System.out.println("讀取完成...");  
          
                // 對獲取的字符串進行處理截取從"provs":到}]},{"id":"01"部分  
                String alluinvRegex = "\"provs\":(.*?)]}";  
                Pattern pattern = Pattern.compile(alluinvRegex);  
                String chn = "";  
                Matcher matcher = pattern.matcher(sb.toString());  
                matcher.find();  
                chn = matcher.group(1);  
                // System.out.println(convertFromHex(tmp));  
          
                // 對截取的中國部分按照省市區進行匹配"id":1,"univs" ...... "country_id":0,"name":"臺灣"  
                String regex2 = "id\":(.*?),\"univs\":(.*?),\"country_id\":0,\"name\":\"(.*?)\"}";  
                Pattern pattern2 = Pattern.compile(regex2);  
                Matcher matcher2 = pattern2.matcher(chn);  
                StringBuilder provsBuilder = new StringBuilder();  
                StringBuilder colBuilder = new StringBuilder();  
                StringBuilder deparBuilder = new StringBuilder();  
                while (matcher2.find()) {  
                    // 咱們項目的sql語句,若是大家數據庫不同,稍微修改一下拉  
                    provsBuilder.append("insert into province(PROID,PRONAME)values('"  
                            + matcher2.group(1) + "','"  
                            + convertFromHex(matcher2.group(3)) + "');\n");  
                    System.out.println("生成-" + convertFromHex(matcher2.group(3))  
                            + "-數據庫");  
                    // 取得學校的ID,還有名字 "id":1001,"name":"\u6e05\u534e\u5927\u5b66"  
                    String colRegex = "id\":(.*?),\"name\":\"(.*?)\"";  
                    Pattern colPattern = Pattern.compile(colRegex);  
                    Matcher colMatcher = colPattern.matcher(matcher2.group(2));  
                    while (colMatcher.find()) {  
                        colBuilder  
                                .append("insert into COLLEGE(PROID,COLID,COLNAME)values('"  
                                        + matcher2.group(1)  
                                        + "','"  
                                        + colMatcher.group(1)  
                                        + "','"  
                                        + convertFromHex(colMatcher.group(2)) + "');\n");  
          
                        System.out.println("生成-" + convertFromHex(colMatcher.group(2))  
                                + "-數據庫");  
          
                        get = new HttpGet(depUrl + colMatcher.group(1));  
                        ResponseHandler<String> depHandler = new BasicResponseHandler();  
                        generateDepartment(client.execute(get, depHandler), colMatcher  
                                .group(1), deparBuilder);  
                    }  
          
                }  
                PrintStream ps = new PrintStream(province);  
                ps.print(provsBuilder.toString());  
                ps.close();  
          
                PrintStream ps2 = new PrintStream(college);  
                ps2.print(colBuilder.toString());  
                ps2.close();  
          
                PrintStream ps3 = new PrintStream(department);  
                ps3.print(deparBuilder.toString());  
                ps3.close();  
                System.err.println("\n\n\n完成數據庫生成,請打開項目目錄查看!");  
            }  
          
            // 這個函數用來處理行查詢到的高校院系 <option  
            // value='&#20013;&#22269;&#35821;&#35328;&#25991;&#23398;&#23398;&#38498;'>&#20013;&#22269;&#35821;&#35328;&#25991;&#23398;&#23398;&#38498;</option>  
            public void generateDepartment(String src, String colid, StringBuilder sb) {  
                String departRegex = "value='(.+?)'>";// 開始用這個正則表達式"value='(.*?)'>";  
                // 後來發現有問題,問題你本身探索吧。  
                Pattern pattern = Pattern.compile(departRegex);  
                Matcher matcher = pattern.matcher(src);  
                while (matcher.find()) {  
                    sb.append("insert into DEPARTMENT(COLID,DEPNAME)values('" + colid  
                            + "','" + convertFromDec(matcher.group(1)) + "');\n");  
                }  
            }  
          
            public static String convertDec(String src) {  
                return Character.toString((char) Integer.parseInt(src, 10));  
            }  
          
            public static String convertHex(String src) {  
                return Character  
                        .toString((char) Integer.parseInt(src.substring(2), 16));  
            }  
          
            // 轉換&#xxxxx;形式Unicode  
            private String convertFromDec(String code) {  
                StringBuffer sb = new StringBuffer(code);  
                int startPos;  
                int endPos;  
                while ((startPos = sb.indexOf("&#")) > -1) {  
                    endPos = sb.indexOf(";");  
                    String tmp = sb.substring(startPos + 2, endPos);  
                    sb.replace(startPos, endPos + 1, Character.toString((char) Integer  
                            .parseInt(tmp, 10)));  
                }  
                return code = sb.toString();  
            }  
          
            // 轉換16進制的Unicode,  
            private String convertFromHex(String code) {  
                StringBuffer sb = new StringBuffer(code);  
                int pos;  
                while ((pos = sb.indexOf("\\u")) > -1) {  
                    String tmp = sb.substring(pos, pos + 6);  
                    sb.replace(pos, pos + 6, Character.toString((char) Integer  
                            .parseInt(tmp.substring(2), 16)));  
                }  
                return code = sb.toString();  
            }  
          
            public static void main(String[] args) throws ClientProtocolException,  
                    IOException {  
                new GenerateSQL();  
            }  
        }

          寫到這裏,基本上完成了高校數據庫的抓取工做,如今只須要導入剛剛生成的sql文件就能夠了。若是你想抓取其餘的信息。原理也應該差很少的吧。只不過要 看看他們有沒有設置session 的權限認證了。若是有,那你得寫一個登錄的東西得到那認證,前面也寫了差很少。應該能夠看懂的。感謝你花這麼長的時間。

           至於標題的 省 高校 院系級聯,好吧, 我騙你了。只不過今天就到此了,還有Asp.net的任務。有了數據庫了,咱還怕寫不出來那個級聯麼?各位看官,若是你要什麼好的級聯,能夠分享一下吧。

     

    聲明:抓取人人網數據僅供學習之用,不對人人網有任何惡意的行爲。

    原文發表在  http://satikey.iteye.com/blog/826988

    相關文章
    相關標籤/搜索