用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