安卓HttpClient+Jsoup+Httpwatch模拟登陆正方教务获取信息

之前想要写一下关于爬数据的文章的,发现时间有点急迫。所以今天在期末考试之前写完跟大家分享一下的我的心得,先上之前的图。

安卓HttpClient+Jsoup+Httpwatch模拟登陆正方教务获取信息
文章图片

今天我也以正方教务体统抓取成绩的例子来给大家讲解,第一次写博客,可能会写的不会,还请大家谅解,不过保证大家看的懂,请耐心看完。
好了,下面开始!!!
第一步:登陆你们的教务,这是我们学习的教务的主页http://jwxt.jit.edu.cn/,进去之后,打开httpWatch,至于没用过的话,自己去研究下,因为里面有将我们需要的Post参数和Post地址都归纳出来的,很好用。如图: 安卓HttpClient+Jsoup+Httpwatch模拟登陆正方教务获取信息
文章图片
点击左边的红点Record进行录制,然后点击登录按钮。 进去之后你会看见HttpWatch刷刷的,出来一大片数据,当然我们的从当中挑选出有用的数据。 安卓HttpClient+Jsoup+Httpwatch模拟登陆正方教务获取信息
文章图片
如图,我们打开上栏中的第一列标签,就是横线的地方。首先我们来观看下数据,请求方法:post,状态值:302,请求的URL:http://jwxt.jit.edu.cn/default2.aspx。 然后,我们来观察下面大圈圈中的数据,这里是Post所带的参数,里面有__ViewStat(我也不知道是什么,不过一起带着Post就好了,这里有个情况,我等会在下面讲,先用绿色标记),Button的值(乱码),lbLanguage(不管,先记下),RadioButtonList1(单选按钮:学生...),TextBox1:账号,TextBox2:密码。Ok....... 下面我们来看一下Button和lbLanguage和RadioButtonList1具体的值,下面的标签打开Stream,如图: 安卓HttpClient+Jsoup+Httpwatch模拟登陆正方教务获取信息
文章图片

看,里面有一些不认识的参数的值。Button="" ,lbLanguage="",RadioButtonList1="%D1%A7%C9%FA",哦了。 大家也可以看看其他标签下的内容,比如Cookies(比较重要),Content呀什么的。。 那么在代码里怎么体现呢,我们先看Post模拟登陆的这一步。

//第一个URL,等着为后面服务 public static final String login_url = "http://jwxt.jit.edu.cn"; //第一个Post模拟登陆的URL public static final String login_url2 = "http://jwxt.jit.edu.cn/default2.aspx";

HttpPost httpPost = new HttpPost(login_url2); //建立一个Post请求,第一步的方法是Post方法嘛 //禁止重定向,由于刚刚Post的状态值是重定向,所以我们要去禁止它,不然网页会乱飞。 httpPost.getParams().setParameter(ClientPNames.HANDLE_REDIRECTS, false); //设置头部信息(头部信息在刚刚的Httpwatch下面Headers标签会有,不过我感觉写多跟写少没多大区别,只是多写没有坏处吧。) httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko"); httpPost.setHeader("Content-Type","application/x-www-form-urlencoded");


//第一种模拟登陆传值 List params = new ArrayList(); //将刚刚获取到的值添加到List的中 params.add(new BasicNameValuePair("__VIEWSTATE","dDwtMTIwMTU3OTE3Nzs7PsvpgBGP9UryEzGkfCRBEu734TJ/")); //params.add(new BasicNameValuePair("__VIEWSTATE", "/wEPDwUJNDcyMzA1MjkxZGRTx3lVi2lf6h+y/PVVH1qMZzouJg==")); //params.add(new BasicNameValuePair("__EVENTVALIDATION", "/wEWCwLkl9v4DwLs0bLrBgLs0fbZDAK/wuqQDgKAqenNDQLN7c0VAuaMg+INAveMotMNAoznisYGArursYYIAt+RzN8IIdJ+D2D5xaddz6rv7AABSyHhO14=")); params.add(new BasicNameValuePair("TextBox1", "1205107009")); // 账号 params.add(new BasicNameValuePair("TextBox2", "*********")); // 密码(密码先保密吧。需要的话私聊) params.add(new BasicNameValuePair("RadioButtonList1", "%D1%A7%C9%FA")); // 学生 params.add(new BasicNameValuePair("Button1", "")); params.add(new BasicNameValuePair("lbLanguage", ""));

【安卓HttpClient+Jsoup+Httpwatch模拟登陆正方教务获取信息】这样的话就把要模拟登陆的所有Post参数全部放到这个List当中了。大家看看在params.add值的时候有没有注意到我把其中的两行给注释掉了?还记得刚刚我记的绿色的标记不?这里我给大家解释一下,由于我们学校教务返回参数的时候会两种不同的情况,就是有时候是只但会一个__VIEWSTATE的值,有时候返回__VIEWSTATE和__EVENTVALIDATION这两个值,而且两个__VIEWSTATE还不一样,这是我用HttpWatch观察到的,不知道你们学校的教务会不会出现这中情况,你们自己注意一下。这里我就直接使用第一种只传递__VIEWSTATE的值给大家做例子,另外一种情况我都注释在第一种情况的下行代码。ps:有的学校没有这种情况,而且居然不要验证码直接就能进入教务主页,比如像南邮。好,贴完刚刚将值加入List的代码,现在我们开始进行模拟登陆了。
try { // 传递参数的时候注意编码使用,否则乱码 httpPost.setEntity(new UrlEncodedFormEntity(params, "GBK")); //响应请求 HttpResponse response = client.execute(httpPost); //获取响应状态码 int Status = response.getStatusLine().getStatusCode(); //302表示重定向状态 if(Status == 302||Status == 301){ //获取响应的cookie值 cookie = response.getFirstHeader("Set-Cookie").getValue(); //获取头部信息中Location的值 location = response.getFirstHeader("Location").getValue(); } }

这一步是设置下Post值的编码方式,然后进行响应请求获取返回的信息。响应的状态码为302,刚刚已经在HttpWatch中看到了。那个这个时候重定向到哪里了呢,这个时候我们发现在Post下面的Get方式,通过Get方式访问的URL是http://jwxt.jit.edu.cn/xs_main.aspx?xh=1205107009,是一个带着查询字符串的URL,那么这个URL怎么获取呢。 这里我来解释一下,重定向后的信息都储存在头部信息中。如下图: 安卓HttpClient+Jsoup+Httpwatch模拟登陆正方教务获取信息
文章图片

不然看出location的值就是主页地址的结尾部分。 这个时候的mianUrl = login_url + location即为http://jwxt.jit.edu.cn/xs_main.aspx?xh=1205107009,然后下一步是Get请求,状态码是200。这样就好做多了,不是吗?
HttpGet get = new HttpGet(mianUrl); //关键点,这里有个关键点就是设置头部信息中的Referer和cookie值,cookie值大家都知道,模拟登陆的时候必须带着cookie一起访问,但是Referer我无法理解,但必须要设置。 //也就是必须指定它的Referer必须为当前访问的URL get.setHeader("Referer", mianUrl); get.addHeader("Cookie", cookie); try { //获取Get响应,如果状态码是200的话表示连接成功 HttpResponse httpResponse = new DefaultHttpClient().execute(get); if(httpResponse.getStatusLine().getStatusCode() == 200){ HttpEntity entity = httpResponse.getEntity(); //获取纯净的主页HTML源码,这里大家可以将mianhtml定义在其他地方 String mainhtml = EntityUtils.toString(entity); }


到这里呢,基本上就等于完事了,获取到主页的HTML纯文本之后就是通过Jsoup包进行解析了,不会使用的话可以去查下它的API文档。
第二步:
下面我们就开始查询我们的成绩的了,首先呢还是HttpWatch工具,依次点击信息查询中的成绩查询,然后点击按学期查询就会查询到在校所有成绩了。
安卓HttpClient+Jsoup+Httpwatch模拟登陆正方教务获取信息
文章图片

这里我们依然会发现因为是按了按钮,所以传递了值,所以还是Post请求,不是状态码是 200。 Post的URL是http://jwxt.jit.edu.cn/xscj_gc.aspx?xh=1205107009&xm=陈凯&gnmkdm=N121605 这里呢依然头疼的问题是URL的获取,我思索观察了半天,发现URL竟然在刚刚获取的mainhtml文本中,是作为按钮跳转的地址。所以这个时候我们就需要用Jsoup来解析这个网页源码了。看代码:
//分析html Document doc = Jsoup.parse(mainhtml); //获取所有链接 Elements links = doc.select("a[href]"); //创建一个缓冲区 StringBuffer sff = new StringBuffer(); for(Element link : links){ //获取所要查询的URL,这里对应地址按钮的名字叫成绩查询 if(link.text().equals("成绩查询")) //获取所要查询的相对地址,获取相对的地址 sff.append(link.attr("href")); }String str = sff.toString(); //返回查询的URL,将主页地址与相对地址连接起来,同样这里的cjcxUrl可以定义在外面 String cjcxUrl = login_url+ "/" + str ;


好了,这样成绩查询的URL就这么出来了。下面是Post请求。在下面标签Stream中查看到传递的参数是Button1="%B0%B4%D1%A7%C6%DA%B2%E9%D1%AF",表示点下了按学期查询,ddlXN和ddlXQ都是空,如果你们学校教务有默认了值,你就按照你们教务的填上就好了。下面是__VIEWSTATE的值,虽然上面出现的是第二种值的情况,但是我这里还是以第一种为例子,我估计你们的学校也许不会出现这种情况。
public static List paramsgra1 = new ArrayList(); //可以发现这是第一种情况值的传递,虽然两种情况不一样,但是放心只要是你们学校的学生,登陆的时候值的情况都是两种,且值都是相同的。 paramsgra1.add(new BasicNameValuePair("__VIEWSTATE","dDwxNjgwNjIxMzEzO3Q8cDxsPHhoOz47bDwxMjA1MTA3MDA5Oz4+O2w8aTwxPjs+O2w8dDw7bDxpPDE+O2k8Mz47aTw1PjtpPDc+O2k8OT47aTwxMT47aTwxMz47aTwxNj47aTwyNj47aTwyNz47aTwyOD47aTwzND47aTwzNj47aTwzOD47aTwzOT47aTw0MT47aTw0Mj47aTw0ND47aTw0Nj47aTw0OD47aTw2MD47aTw2NT47PjtsPHQ8cDxwPGw8VGV4dDs+O2w85a2m5Y+377yaMTIwNTEwNzAwOTs+Pjs+Ozs+O3Q8cDxwPGw8VGV4dDs+O2w85aeT5ZCN77ya6ZmI5YevOz4+Oz47Oz47dDxwPHA8bDxUZXh0Oz47bDzlrabpmaLvvJrkv6Hmga/mioDmnK/lrabpmaI7Pj47Pjs7Pjt0PHA8cDxsPFRleHQ7PjtsPOS4k+S4mu+8mjs+Pjs+Ozs+O3Q8cDxwPGw8VGV4dDs+O2w86L2v5Lu25bel56iLKOacrCk7Pj47Pjs7Pjt0PHA8cDxsPFRleHQ7PjtsPOihjOaUv+ePre+8mjEy6L2v5Lu25bel56iL77yIMe+8iTs+Pjs+Ozs+O3Q8cDxwPGw8VGV4dDs+O2w8MjAxMjA1MDc7Pj47Pjs7Pjt0PHQ8O3Q8aTwxNT47QDxcZTsyMDAxLTIwMDI7MjAwMi0yMDAzOzIwMDMtMjAwNDsyMDA0LTIwMDU7MjAwNS0yMDA2OzIwMDYtMjAwNzsyMDA3LTIwMDg7MjAwOC0yMDA5OzIwMDktMjAxMDsyMDEwLTIwMTE7MjAxMS0yMDEyOzIwMTItMjAxMzsyMDEzLTIwMTQ7MjAxNC0yMDE1Oz47QDxcZTsyMDAxLTIwMDI7MjAwMi0yMDAzOzIwMDMtMjAwNDsyMDA0LTIwMDU7MjAwNS0yMDA2OzIwMDYtMjAwNzsyMDA3LTIwMDg7MjAwOC0yMDA5OzIwMDktMjAxMDsyMDEwLTIwMTE7MjAxMS0yMDEyOzIwMTItMjAxMzsyMDEzLTIwMTQ7MjAxNC0yMDE1Oz4+Oz47Oz47dDxwPDtwPGw8b25jbGljazs+O2w8d2luZG93LnByaW50KClcOzs+Pj47Oz47dDxwPDtwPGw8b25jbGljazs+O2w8d2luZG93LmNsb3NlKClcOzs+Pj47Oz47dDxwPHA8bDxWaXNpYmxlOz47bDxvPHQ+Oz4+Oz47Oz47dDxAMDw7Ozs7Ozs7Ozs7Pjs7Pjt0PEAwPDs7Ozs7Ozs7Ozs+Ozs+O3Q8QDA8Ozs7Ozs7Ozs7Oz47Oz47dDxAMDw7Ozs7Ozs7Ozs7Pjs7Pjt0PEAwPDs7Ozs7Ozs7Ozs+Ozs+O3Q8QDA8Ozs7Ozs7Ozs7Oz47Oz47dDxAMDw7Ozs7Ozs7Ozs7Pjs7Pjt0PEAwPHA8cDxsPFZpc2libGU7PjtsPG88Zj47Pj47Pjs7Ozs7Ozs7Ozs+Ozs+O3Q8QDA8cDxwPGw8VmlzaWJsZTs+O2w8bzxmPjs+Pjs+Ozs7Ozs7Ozs7Oz47Oz47dDxwPHA8bDxUZXh0Oz47bDxZUFhZOz4+Oz47Oz47dDxAMDw7Ozs7Ozs7Ozs7Pjs7Pjs+Pjs+Pjs+BX9xNmfFtmx2lSdmzHhoR2MpxKw=")); //paramsgra1.add(new BasicNameValuePair("__VIEWSTATE", "/wEPDwULLTE0NDU1MDczNjEPFgIeAnhoBQoxMjA1MTA3MDA5FgICAQ9kFiwCAQ8PFgIeBFRleHQFE+WtpuWPt++8mjEyMDUxMDcwMDlkZAIDDw8WAh8BBQ/lp5PlkI3vvJrpmYjlh69kZAIFDw8WAh8BBRvlrabpmaLvvJrkv6Hmga/mioDmnK/lrabpmaJkZAIHDw8WAh8BBQnkuJPkuJrvvJpkZAIJDw8WAh8BBRHova/ku7blt6XnqIso5pysKWRkAgsPDxYCHwEFIeihjOaUv+ePre+8mjEy6L2v5Lu25bel56iL77yIMe+8iWRkAg0PDxYCHwEFCDIwMTIwNTA3ZGQCEA8QZBAVDwAJMjAwMS0yMDAyCTIwMDItMjAwMwkyMDAzLTIwMDQJMjAwNC0yMDA1CTIwMDUtMjAwNgkyMDA2LTIwMDcJMjAwNy0yMDA4CTIwMDgtMjAwOQkyMDA5LTIwMTAJMjAxMC0yMDExCTIwMTEtMjAxMgkyMDEyLTIwMTMJMjAxMy0yMDE0CTIwMTQtMjAxNRUPAAkyMDAxLTIwMDIJMjAwMi0yMDAzCTIwMDMtMjAwNAkyMDA0LTIwMDUJMjAwNS0yMDA2CTIwMDYtMjAwNwkyMDA3LTIwMDgJMjAwOC0yMDA5CTIwMDktMjAxMAkyMDEwLTIwMTEJMjAxMS0yMDEyCTIwMTItMjAxMwkyMDEzLTIwMTQJMjAxNC0yMDE1FCsDD2dnZ2dnZ2dnZ2dnZ2dnZ2RkAhoPD2QWAh4Hb25jbGljawUPd2luZG93LnByaW50KCk7ZAIbDw9kFgIfAgUPd2luZG93LmNsb3NlKCk7ZAIcDw8WAh4HVmlzaWJsZWdkZAIiDzwrAAsAZAIkDzwrAAsAZAImDzwrAAsAZAInDzwrAAsAZAIpDzwrAAsAZAIqDzwrAAsAZAIsDzwrAAsAZAIuDzwrAAsBAA8WAh8DaGRkAjAPPCsACwEADxYCHwNoZGQCPA8PFgIfAQUEWVBYWWRkAkEPPCsACwBkZDO/nriqRqGoEf9J81ywNopcRE6l")); //paramsgra1.add(new BasicNameValuePair("__EVENTVALIDATION", "/wEWGwKpihYC7sDphAUC/Onx6gUC/+nt/AsC9umZ6QQC+em18woC+Omh6QcC++nd8wUC8unJ6QYC9emlkAYC+unNqwgCoKvKxQ4Co6uOhAgCoquSxAkCvavWhAsCvKu6xAgC38DphAUC0K/D6gkC0a/D6gkC0q/D6gkCjOeKxgYCoMKT8Q0Cu6uxhggCz4a6sQ8Chdn12wMC1pTPmwIC6u/XxglYyInHMSCwi1+Sj12nmKQ31hu7wg==")); paramsgra1.add(new BasicNameValuePair("Button1","%B0%B4%D1%A7%C6%DA%B2%E9%D1%AF")); paramsgra1.add(new BasicNameValuePair("ddlXQ","")); paramsgra1.add(new BasicNameValuePair("ddlXN",""));

以上就是你要进行成绩查询所要Post的值,下面呢进行Post请求
HttpPost cjpost = new HttpPost(login_cjcx); //这一步是关键,跟刚刚一样,这里需要将cookie和Referer的设置一下,其他的话,不设置应该也没什么事,他们都是在HttpWatch的Post头信息中,你们自己去看看吧 cjpost.setHeader("Referer", login_cjcx); cjpost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko"); cjpost.setHeader("Content-Type","application/x-www-form-urlencoded"); cjpost.setHeader("Accept", "text/html, application/xhtml+xml, */*"); cjpost.setHeader("Connection", "Keep-Alive"); cjpost.addHeader("Cookie",connJK.loginCookie()); try{ //Post请求获取成绩的Html。将刚刚的成绩查询的List拿过来,设置编码。 cjpost.setEntity(new UrlEncodedFormEntity(paramsgra1, "gb2312")); HttpResponse response = new DefaultHttpClient().execute(cjpost); HttpEntity entity = response.getEntity(); //获取成绩的HTML源码 cjHtml = EntityUtils.toString(entity); //自己打印出来看看
//System.out.println(cjHtml); //关闭连接 cjpost.abort(); }


经过上面一系列的讲解,我们就可以获取成绩的HTML源码了,里面都是我们所需要的成绩。 同理,下面我们进行解析。
//处理获取的分数页面 Document cjdoc = Jsoup.parse(cjHtml); // 获取到每行数据的选择器,这里的选择器你们可以观察下HTML代码,这里就不多说了。 String rowRegex = "div.main_box div.mid_box span.formbox table#Datagrid1.datelist tbody tr"; // 每行的数据元素 Elements rowElements = cjdoc.select(rowRegex); List list = new ArrayList(); for (int i = 0; i < rowElements.size(); i++) { Elements elements = rowElements.get(i).select("td"); //将每个的内容存放到map中 Map map = new HashMap(); //学年信息 map.put("k_xuenian", elements.get(0).text()); //学期信息 map.put("k_xueqi", elements.get(1).text()); //课程名字 map.put("k_kcname", elements.get(3).text()); //学分 map.put("k_xuefen", elements.get(6).text()); //几点 map.put("k_jidian", elements.get(7).text()); //成绩 map.put("k_chengji", elements.get(8).text()); list.add(map); } //创建一个简单适配器,这里也不多说了。 SimpleAdapter adapter = new SimpleAdapter(this, list, R.layout.k_kccx, new String[] { "k_xuenian","k_xueqi","k_kcname","k_xuefen","k_jidian","k_chengji"}, new int[] { R.id.k_xuenian, R.id.k_xueqi,R.id.k_kcname,R.id.k_xuefen,R.id.k_jidian,R.id.k_chengji}); //通过一个ListView将这个适配器显示出来,然后就出现那种黑屏效果 ListView listview = new ListView(this); listview.setAdapter(adapter);


PS: 以上显示之后,你会发现像第一张图那样的丑陋,所有该怎么处理数据,大家根据自己的能力和爱好自行处理。 至于其他的课表,考试时间呀什么的,大家自己去摸索摸索。。课表的话自己要经过一大堆的算法才能做出课程格子或者超级课表那种效果。 本人团队的话也只实现他们的初级版本。。。 最后大家去手动敲代码吧、敲敲更健康。。。 最后大家有什么不理解可以留言给我,大家可以互相交流交流。。。


    推荐阅读