用HttpClient抓取人人网高校数据库(省,高校,院系三级级联)


来自:http://www.iteye.com/topic/826988

更新备注:将src文件改成了一个完整的项目,解压后可以直接导入到Eclipse中去,省去大家配置(项目乱码请改项目属性为GBK)。另外,如果你要登陆人人网 的话,需要申请一个人人网账号。这里提供公用的:\

lei.d0809@gmail.com

java123456

请自行修改RenRenNotify.java 对应的东西。


首先文章有点长,需要点耐心。这里我是一步一步的做的。。。。比较的细,如果你是代码达人,那你就直接下载代码吧。

有人说图片看不清,我抱歉,第一次咱的图片不完美,你把图片在浏览器上拖动到新窗口,就可以看到你大图了。


需求来源,最近学校的课程项目需要一个省,高校,院系的三级级联的东西,这下麻烦了。全国那么多的高校,而且每一个高校的院系设置又不一样,我们小组只有六个人,而且技术都不咋地,要统计那么多的数据,我们估计这学期就别想完成这个项目了。但是我们知道人人网,开心网,腾讯微博上都要高校的数据库,于是想法就产生了:

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构造请求的时候,就应该将这四个参数的给附带进去,部分代码如下:

Java代码
  1. //将要发送的数据封包
  2. List<NameValuePair>params=newArrayList<NameValuePair>();
  3. params.add(newBasicNameValuePair("email",this.email));
  4. params.add(newBasicNameValuePair("password",this.password));
  5. params.add(newBasicNameValuePair("origURL",origURL));
  6. params.add(newBasicNameValuePair("domain",domain));

接下来我们来完整登陆一次:

当输入正确的用户名密码,点击登陆,我们又获得什么样的东西呢?参见如下:

返回的内容意思大概是 地址转变了要进行跳转,而且返回的相应头是 302,文件修改了。再看一下 返回的消息头:

有一个Location,应该是要我们跳转的地址。这样我们应该可以访问人人网的任意连接资源了。

登录过程的完整代码(包含读嗅探指定资源的链接):

Java代码
  1. importjava.io.IOException;
  2. importjava.io.UnsupportedEncodingException;
  3. importjava.util.ArrayList;
  4. importjava.util.List;
  5. importorg.apache.http.HttpResponse;
  6. importorg.apache.http.NameValuePair;
  7. importorg.apache.http.client.ClientProtocolException;
  8. importorg.apache.http.client.ResponseHandler;
  9. importorg.apache.http.client.entity.UrlEncodedFormEntity;
  10. importorg.apache.http.client.methods.HttpGet;
  11. importorg.apache.http.client.methods.HttpPost;
  12. importorg.apache.http.impl.client.BasicResponseHandler;
  13. importorg.apache.http.impl.client.DefaultHttpClient;
  14. importorg.apache.http.message.BasicNameValuePair;
  15. importorg.apache.http.protocol.HTTP;
  16. /**
  17. *
  18. *
  19. *Author:Saitkey<lei_d@foxmail.com>
  20. */
  21. publicclassRenRenNotify{
  22. privatestaticHttpResponseresponse;
  23. privatestaticDefaultHttpClienthttpClient;
  24. publicRenRenNotify(StringuserName,Stringpassword){
  25. this.httpClient=newDefaultHttpClient();
  26. StringloginForm="http://www.renren.com/PLogin.do";
  27. StringorigURL="http://www.renren.com/Home.do";
  28. Stringdomain="renren.com";
  29. //在首页表单上是隐藏的抓包后分析,并没有发送到服务器
  30. //StringautoLogin="true";
  31. //构造一个POST请求,利用Httclient提供的包
  32. HttpPosthttpPost=newHttpPost(loginForm);
  33. //将要发送的数据封包
  34. List<NameValuePair>params=newArrayList<NameValuePair>();
  35. params.add(newBasicNameValuePair("email",userName));
  36. params.add(newBasicNameValuePair("password",password));
  37. params.add(newBasicNameValuePair("origURL",origURL));
  38. params.add(newBasicNameValuePair("domain",domain));
  39. //封包添加到Post请求
  40. try{
  41. httpPost.setEntity(newUrlEncodedFormEntity(params,HTTP.UTF_8));
  42. }catch(UnsupportedEncodingExceptione1){
  43. //TODOAuto-generatedcatchblock
  44. e1.printStackTrace();
  45. }
  46. //将get和post方法包含到一个函数里面去,这里就是登陆过程了。
  47. response=postMethod(httpPost);
  48. /*
  49. *有跳转System.out.println(response.getStatusLine());//返回302
  50. *Header[]headers=response.getAllHeaders();for(inti=0;i<
  51. *headers.length;i++){Headerheader=headers[i];
  52. *System.out.println(header.getName()+":"+header.getValue());}
  53. */
  54. //读取跳转的地址
  55. //StringredirectUrl=response.getFirstHeader("Location").getValue();
  56. //查看一下跳转过后,都出现哪些内容.
  57. //response=getMethod(redirectUrl);//函数见后面
  58. //System.out.println(response.getStatusLine());//HTTP/1.1200OK
  59. //读取一下主页都有什么内容已经登陆进去
  60. //System.out.println(readHtml("http://www.renren.com/home"));
  61. }
  62. //嗅探指定页面的代码
  63. publicStringnotify(Stringurl){
  64. HttpGetget=newHttpGet(url);
  65. ResponseHandler<String>responseHandler=newBasicResponseHandler();
  66. Stringtxt=null;
  67. try{
  68. txt=httpClient.execute(get,responseHandler);
  69. }catch(ClientProtocolExceptione){
  70. e.printStackTrace();
  71. }catch(IOExceptione){
  72. e.printStackTrace();
  73. }finally{
  74. get.abort();
  75. }
  76. returntxt;
  77. }
  78. //用post方法向服务器请求并获得响应,因为post方法要封装参数,因此在函数外部封装好传参
  79. publicHttpResponsepostMethod(HttpPostpost){
  80. HttpResponseresp=null;
  81. try{
  82. resp=httpClient.execute(post);
  83. }catch(ClientProtocolExceptione){
  84. e.printStackTrace();
  85. }catch(IOExceptione){
  86. e.printStackTrace();
  87. }finally{
  88. post.abort();
  89. }
  90. returnresp;
  91. }
  92. //用get方法向服务器请求并获得响应
  93. publicHttpResponsegetMethod(Stringurl){
  94. HttpGetget=newHttpGet(url);
  95. HttpResponseresp=null;
  96. try{
  97. resp=httpClient.execute(get);
  98. }catch(ClientProtocolExceptione){
  99. e.printStackTrace();
  100. }catch(IOExceptione){
  101. e.printStackTrace();
  102. }finally{
  103. get.abort();
  104. }
  105. returnresp;
  106. }
  107. publicstaticvoidmain(String[]args){
  108. RenRenNotifynotify=newRenRenNotify("[你的用户名]",
  109. "[你的密码]");
  110. System.out.println(notify
  111. .notify("http://www.renren.com/home"));
  112. }
  113. }

好了,现在登录了。我们去修改自己的教育信息吧,首先自然是进入相应的页面:

当我们进入了修改教育信息的时候,我们发现HttpAnalyzer里面多了如下内容:

注意红色的内容。这里应该是所有高校的信息。体积也达到了402kb,看一下里面的内容:

这个里面有个奇怪的东西:\u4e2d\u56fd 这个是 “中国”的意思,经过转码了。用JavaScript 直接 alert('u4e2d\u56fd '),就明了了。

对于一长串的字符,可以用下面的代码进行回来(code是源):

Java代码
  1. StringBuffersb=newStringBuffer(code);
  2. intpos;
  3. while((pos=sb.indexOf("\\u"))>-1){
  4. Stringtmp=sb.substring(pos,pos+6);
  5. sb.replace(pos,pos+6,Character.toString((char)Integer
  6. .parseInt(tmp.substring(2),16)));
  7. }
  8. code=sb.toString();

接下来,我们选择一个高校看看,HttpAnalyzer里面出现如下的信息:

再来一下:

所以通过上面两次抓取,我们应该得出一个例子,那就是:我们选择好了一个大学,就会相应的得出他的ID,然后这时候会想服务器发送一个请求查询:http://www.renren.com/GetDep.do?id=13003,其中id后面的便是高校的代号了。然后返回的是一串html代码,如下:

这里同样是奇怪的一串数字,这种也是Unicode,不过是十进制的,而且在编码的前后分别加上“&#”和“;”就可以形成Html实体字符,可以在网页上直接显示。

对于以上的代码,我们也参照上面写一个转换的代码:如下:

Java代码
  1. StringBuffersb=newStringBuffer(code);
  2. intpos;
  3. while((pos=sb.indexOf("&#"))>-1){
  4. Stringtmp=sb.substring(pos+2,pos+7);
  5. sb.replace(pos,pos+8,Character.toString((char)Integer.parseInt(tmp,10)));
  6. }
  7. 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包,需要自己配置一下。如果不会,请留言吧,我争取重新打包再上传上来。

Java代码
  1. importjava.io.File;
  2. importjava.io.IOException;
  3. importjava.io.PrintStream;
  4. importjava.util.regex.Matcher;
  5. importjava.util.regex.Pattern;
  6. importorg.apache.http.client.ClientProtocolException;
  7. importorg.apache.http.client.HttpClient;
  8. importorg.apache.http.client.ResponseHandler;
  9. importorg.apache.http.client.methods.HttpGet;
  10. importorg.apache.http.impl.client.BasicResponseHandler;
  11. importorg.apache.http.impl.client.DefaultHttpClient;
  12. /**
  13. *
  14. *
  15. *Author:Saitkey<lei_d@foxmail.com>
  16. */
  17. publicclassGenerateSQL{
  18. //构建省的sql文件
  19. privateFileprovince=newFile("provice.sql");
  20. //构建高校的sql文件
  21. privateFilecollege=newFile("college.sql");
  22. //构建院系的sql文件
  23. privateFiledepartment=newFile("department.sql");
  24. GenerateSQL()throwsClientProtocolException,IOException{
  25. HttpClientclient=newDefaultHttpClient();
  26. ResponseHandler<String>responseHandler=newBasicResponseHandler();
  27. StringdepUrl="http://www.renren.com/GetDep.do?id=";
  28. Stringallunivs="http://s.xnimg.cn/a13819/allunivlist.js";
  29. HttpGetget=newHttpGet(allunivs);
  30. System.out.println("读取高校信息...");
  31. StringBuffersb=newStringBuffer(client.execute(get,responseHandler));
  32. System.out.println("读取完成...");
  33. //对获取的字符串进行处理截取从"provs":到}]},{"id":"01"部分
  34. StringalluinvRegex="\"provs\":(.*?)]}";
  35. Patternpattern=Pattern.compile(alluinvRegex);
  36. Stringchn="";
  37. Matchermatcher=pattern.matcher(sb.toString());
  38. matcher.find();
  39. chn=matcher.group(1);
  40. //System.out.println(convertFromHex(tmp));
  41. //对截取的中国部分按照省市区进行匹配"id":1,"univs"......"country_id":0,"name":"台湾"
  42. Stringregex2="id\":(.*?),\"univs\":(.*?),\"country_id\":0,\"name\":\"(.*?)\"}";
  43. Patternpattern2=Pattern.compile(regex2);
  44. Matchermatcher2=pattern2.matcher(chn);
  45. StringBuilderprovsBuilder=newStringBuilder();
  46. StringBuildercolBuilder=newStringBuilder();
  47. StringBuilderdeparBuilder=newStringBuilder();
  48. while(matcher2.find()){
  49. //我们项目的sql语句,如果你们数据库不一样,稍微修改一下拉
  50. provsBuilder.append("insertintoprovince(PROID,PRONAME)values('"
  51. +matcher2.group(1)+"','"
  52. +convertFromHex(matcher2.group(3))+"');\n");
  53. System.out.println("生成-"+convertFromHex(matcher2.group(3))
  54. +"-数据库");
  55. //取得学校的ID,还有名字"id":1001,"name":"\u6e05\u534e\u5927\u5b66"
  56. StringcolRegex="id\":(.*?),\"name\":\"(.*?)\"";
  57. PatterncolPattern=Pattern.compile(colRegex);
  58. MatchercolMatcher=colPattern.matcher(matcher2.group(2));
  59. while(colMatcher.find()){
  60. colBuilder
  61. .append("insertintoCOLLEGE(PROID,COLID,COLNAME)values('"
  62. +matcher2.group(1)
  63. +"','"
  64. +colMatcher.group(1)
  65. +"','"
  66. +convertFromHex(colMatcher.group(2))+"');\n");
  67. System.out.println("生成-"+convertFromHex(colMatcher.group(2))
  68. +"-数据库");
  69. get=newHttpGet(depUrl+colMatcher.group(1));
  70. ResponseHandler<String>depHandler=newBasicResponseHandler();
  71. generateDepartment(client.execute(get,depHandler),colMatcher
  72. .group(1),deparBuilder);
  73. }
  74. }
  75. PrintStreamps=newPrintStream(province);
  76. ps.print(provsBuilder.toString());
  77. ps.close();
  78. PrintStreamps2=newPrintStream(college);
  79. ps2.print(colBuilder.toString());
  80. ps2.close();
  81. PrintStreamps3=newPrintStream(department);
  82. ps3.print(deparBuilder.toString());
  83. ps3.close();
  84. System.err.println("\n\n\n完成数据库生成,请打开项目目录查看!");
  85. }
  86. //这个函数用来处理行查询到的高校院系<option
  87. //value='&#20013;&#22269;&#35821;&#35328;&#25991;&#23398;&#23398;&#38498;'>&#20013;&#22269;&#35821;&#35328;&#25991;&#23398;&#23398;&#38498;</option>
  88. publicvoidgenerateDepartment(Stringsrc,Stringcolid,StringBuildersb){
  89. StringdepartRegex="value='(.+?)'>";//开始用这个正则表达式"value='(.*?)'>";
  90. //后来发现有问题,问题你自己探索吧。
  91. Patternpattern=Pattern.compile(departRegex);
  92. Matchermatcher=pattern.matcher(src);
  93. while(matcher.find()){
  94. sb.append("insertintoDEPARTMENT(COLID,DEPNAME)values('"+colid
  95. +"','"+convertFromDec(matcher.group(1))+"');\n");
  96. }
  97. }
  98. publicstaticStringconvertDec(Stringsrc){
  99. returnCharacter.toString((char)Integer.parseInt(src,10));
  100. }
  101. publicstaticStringconvertHex(Stringsrc){
  102. returnCharacter
  103. .toString((char)Integer.parseInt(src.substring(2),16));
  104. }
  105. //转换&#xxxxx;形式Unicode
  106. privateStringconvertFromDec(Stringcode){
  107. StringBuffersb=newStringBuffer(code);
  108. intstartPos;
  109. intendPos;
  110. while((startPos=sb.indexOf("&#"))>-1){
  111. endPos=sb.indexOf(";");
  112. Stringtmp=sb.substring(startPos+2,endPos);
  113. sb.replace(startPos,endPos+1,Character.toString((char)Integer
  114. .parseInt(tmp,10)));
  115. }
  116. returncode=sb.toString();
  117. }
  118. //转换16进制的Unicode,
  119. privateStringconvertFromHex(Stringcode){
  120. StringBuffersb=newStringBuffer(code);
  121. intpos;
  122. while((pos=sb.indexOf("\\u"))>-1){
  123. Stringtmp=sb.substring(pos,pos+6);
  124. sb.replace(pos,pos+6,Character.toString((char)Integer
  125. .parseInt(tmp.substring(2),16)));
  126. }
  127. returncode=sb.toString();
  128. }
  129. publicstaticvoidmain(String[]args)throwsClientProtocolException,
  130. IOException{
  131. newGenerateSQL();
  132. }
  133. }

写到这里,基本上完成了高校数据库的抓取工作,现在只需要导入刚刚生成的sql文件就可以了。如果你想抓取其他的信息。原理也应该差不多的吧。只不过要看看他们有没有设置session 的权限认证了。如果有,那你得写一个登陆的东西获得那认证,前面也写了差不多。应该可以看懂的。感谢你花这么长的时间。

至于标题的 省 高校 院系级联,好吧, 我骗你了。只不过今天就到此了,还有Asp.net的任务。有了数据库了,咱还怕写不出来那个级联么?各位看官,如果你要什么好的级联,可以分享一下吧。

声明:抓取人人网数据仅供学习之用,不对人人网有任何恶意的行为。

长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号





    联系我们

    欢迎来到TinyMind。

    关于TinyMind的内容或商务合作、网站建议,举报不良信息等均可联系我们。

    TinyMind客服邮箱:support@tinymind.net.cn