从爬取华科hub教务系统课表浅谈Java信息抓取的实现 —— import java.*;

原创文章与源码,如果转载请注明来源。  

开发环境:Myeclipse,依赖包:apache-httpclientJsoupbase64

一、概述

华科大的教务系统(hub系统)做的算是比较好的,无论是界面还是其安全性来说,都是很不错的。大家可以用浏览器的调试工具F12看一下里面的源码。关于它的安全性,在后面会为大家提到。而在布局方面,用<div>代替了一些传统网站使用的<iframe>,导航栏也是使用的比较流行的插件。

其首页地址为http://hub.hust.edu.cn/index.jsp,整个系统用Java开发。我们现在要做的是类似于超级课程表、课程格子之类的功能:输入一个学生的教务系统账号、密码,得到Ta的课程表信息。点击进入课表查询,我们发现了这样的页面:

这就是我们需要的结果。其实思路很简单,用java访问这个链接,拿到Html字符串,然后解析链接等需要的数据。

这个页面的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请求之前,必须实现模拟登录。

二、JAVA中GET/POST请求的实现

在进行模拟登录之前,我们需要了解一些基本知识。

首先请看关于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包中)。

三、模拟登录的实现

一般地,在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也就是表示登录已经成功,可以跳转到其他页面了。)

四、课表的获取

在第三部登录成功之后,我们发现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月的课表。

至此,华科大教务系统课表爬取完成!

五、总结

  我的代码的编程思路:

  你可以直接在我的基础上扩展,适用于其他学校的“课程格子”。

  你可以选择继承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

时间: 2024-10-03 19:50:12

从爬取华科hub教务系统课表浅谈Java信息抓取的实现 —— import java.*;的相关文章

httpclient3+jsoup获取正方教务系统课表

运用httpclient3+jsoup获取正方教务系统课表 import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Scanner; i

数据抓取的艺术(一):Selenium+Phantomjs数据抓取环境配置

数据抓取的艺术(一):Selenium+Phantomjs数据抓取环境配置 2013-05-15 15:08:14 分类: Python/Ruby 数据抓取是一门艺术,和其他软件不同,世界上不存在完美的.一致的.通用的抓取工具.为了不同的目的,需要定制不同的代码.不过,我们不必Start from Scratch,已经有许多的基本工具.基本方法和基础框架可供使用.不同的工具.不同的方法.不同的框架的特点也不同.了解这些工具.方法和框架是首要任务,接下来就需要明白它们的差异都在哪里.什么情境该用什

网易新闻页面信息抓取 -- htmlagilitypack搭配scrapysharp

最近在弄网页爬虫这方面的,上网看到关于htmlagilitypack搭配scrapysharp的文章,于是决定试一试~ 于是到https://www.nuget.org/packages/ScrapySharp去看看, 看到这句下载提示:To install ScrapySharp, run the following command in the Package Manager Console PM> Install-Package ScrapySharp 接下去我就去找package man

Java爬虫,信息抓取的实现

转载请注明出处:http://blog.csdn.net/lmj623565791/article/details/23272657 今天公司有个需求,需要做一些指定网站查询后的数据的抓取,于是花了点时间写了个demo供演示使用. 思想很简单:就是通过Java访问的链接,然后拿到html字符串,然后就是解析链接等需要的数据. 技术上使用Jsoup方便页面的解析,当然Jsoup很方便,也很简单,一行代码就能知道怎么用了: [java] view plaincopy Document doc = J

Atitit.web的自动化操作与信息抓取&#160;attilax总结

Atitit.web的自动化操作与信息抓取 attilax总结 1. Web操作自动化工具,可以简单的划分为2大派系: 1.录制回放 2.手工编写0 U' z; D! s2 d/ Q! ^1 2. 常用的软件1 2.1. swt (ie com)  ,nativeswing2 2.2. 基于 selenium2 2.3. Imacro for firefox插件2 2.4. Zenno Poster2 2.5. Ubot在Zenno Poster出来以前应该是最火爆的Web自动化工具(BHW最常

iOS—网络实用技术OC篇&amp;网络爬虫-使用java语言抓取网络数据

网络爬虫-使用java语言抓取网络数据 前提:熟悉java语法(能看懂就行) 准备阶段:从网页中获取html代码 实战阶段:将对应的html代码使用java语言解析出来,最后保存到plist文件 上一片文章已经介绍我们可以使用两个方式来抓取网络数据实现网络爬虫,并且大致介绍了一下怎么使用正则表达式去实现数据的抓取 由于笔者曾经学过一段时间java和android相关的技术,今天就讲讲怎么使用java去抓取网络数据,关于Python有机会等笔者好好研究一下再来分享,但其实会一种就可以,除非你的需求

如何使用JAVA语言抓取某个网页中的邮箱地址

现实生活中咱们常常在浏览网页时看到自己需要的信息,但由于信息过于庞大而又不能逐个保存下来. 接下来,咱们就以获取邮箱地址为例,使用java语言抓取网页中的邮箱地址 实现思路如下: 1.使用Java.net.URL对象,绑定网络上某一个网页的地址 2.通过java.net.URL对象的openConnection()方法获得一个URLConnection对象 3.通过URLConnection对象的getInputStream()方法获得该网络文件的输入流对象InputStream 4.循环读取流

iOS开发——网络实用技术OC篇&amp;网络爬虫-使用java语言抓取网络数据

网络爬虫-使用java语言抓取网络数据 前提:熟悉java语法(能看懂就行) 准备阶段:从网页中获取html代码 实战阶段:将对应的html代码使用java语言解析出来,最后保存到plist文件 上一片文章已经介绍我们可以使用两个方式来抓取网络数据实现网络爬虫,并且大致介绍了一下怎么使用正则表达式去实现数据的抓取 由于笔者曾经学过一段时间java和android相关的技术,今天就讲讲怎么使用java去抓取网络数据,关于Python有机会等笔者好好研究一下再来分享,但其实会一种就可以,除非你的需求

【最新原创】中国移动(中国联通)_通信账单,详单,个人信息抓取爬虫代码

概要: 1.因为公司需要,就花了一点时间写了一下三大运营商通信数据的抓取,涉及到Web上你所看得到的一切数据. 代码没啥技术含量,重点在于抓包分析过程.期间遇到了很多未知的困难,都一一克服了. 2.由于抓取数据的隐私性,我们的抓包是假设在用户已知自己数据被抓取,并且同意告知短信验证码的情况下进行的, 不属于黑客范畴! 3.整个过程,包括重建数据库表结构,解析json等如同逆向运营商的数据库一般.总体来说,三大运营商更新频率不算频繁,还算较稳定,数据结构,网页结构等都不会做很大的变动. 整体效果如