基于HttpClient的正方教务系统模拟登录(带验证码)

【注:原文链接  csdn-java爬虫】

PS:恩,由于最近在学web和简单的http协议,所以心血来潮想用java写个爬虫来爬取学校官网(正方教务系统)个人主页的基础信息(课程信息、成绩……),其实在之前学过java基础教程的时候就可以写的,但后知后觉总是在当前阶段做之前阶段应该完成的任务!或许这就是菜鸟吧~~~。


写在前面:其实写java爬虫的话,一般使用三种方法:自带库类(urlconnection),外库(httpclient)和文档解析库(jsoup);其中jsoup这个第三方库重点用于对html文档的解析,在官方文档中除了Jsoup.connect()方法进行访问网页外并没有多余的方法,而且在访问网页是需要登录的网站爬虫中(get和post方法访问),大多使用前两种方法。又由于httpclient相对于自带库更加灵活高效,所以在该爬虫中使用httpclient访问+Jsoup解析文档结合操作。


注意:作为一个小白,在尝试每一个别人已经实现的而自己未曾实现的项目时,都会有或多或少的问题,而这个爬虫项目对于我来说也是如此,所以才会写下这篇博文作为填坑的记录,也方便别的刚入门的小白能够顺利完成。如果正在阅读的你也是一个刚入门的小白想用java写一个爬取学校官网的爬虫(大神勿喷请绕道~~~),那就很幸运了,只要你按照以下文章内容循序渐进,那一定会得到你想要的结果。(当然,在我接下来描述的项目中,是以正方教务系统来实现的,如果你的学校官网是其他的,其实掌握核心技术,也是大同小异)


前期准备:

项目环境:火狐浏览器,eclipseIDE

所需外库jar包:

commons-codec-1.3.jar

commons-httpclient-3.1.jar

commons-logging-1.0.4.jar

jsouop-1.8.1.jar

注意:这几个jar包是必不可少的,可能会有人疑惑:不是只要httpclient包就好了嘛,怎么还要其他两个包。其实你只要知道这三个包的版本都是很老的,Apache已经不再维护更新,而且这三个包是相互依赖的,必不可缺。(由于我一开始下载的就是3.1版本,不想再改4.X的,因为里面的一些方法函数的使用是不一样的。下载的话我已经都分享到csdn中,可以进行下载:httpclient-jar包下载),导入eclipse中的话就不用细说了,结果如下图。

基于HttpClient的正方教务系统模拟登录(带验证码)_第1张图片

破土动工:

【注】爬虫是什么,就不需要赘述了,如果是通过url爬取没有权限的网页是很简单的,直接用Jsoup就可以完成。但是当我们在爬取一些需要登录的网站时,我们如果还是直接访问该url的话会重新返回到登录首页,所以我们就要进行模拟登录,至于什么是模拟登录,就是将账号密码验证码post给网站的服务器,通过记住其cookie来访问登录后的其他页面(其实这是先行知识,之所以要提一嘴,是因为我在开始的时候也是一头雾水状态)。

首先分析模拟登录过程,先打开火狐浏览器,打开学校教务系统登录界面。然后按F12打开抓包界面,按正常方式进行登录。(~~一不小心把学校暴露了……)

基于HttpClient的正方教务系统模拟登录(带验证码)_第2张图片

成功登录以后接而分析登录过程的http状态,你会发现其中有一个状态码为302的POST方法和200的GET方法,然后打开这两个提交数据,可以看见下图。

基于HttpClient的正方教务系统模拟登录(带验证码)_第3张图片
基于HttpClient的正方教务系统模拟登录(带验证码)_第4张图片


其中红框内就是请求数据界面的地址。

继而分析提交数据,在抓包分析的时候会发现其中有很多的参数,其实就是在登录的过程中浏览器会自动将你手动输入的账号密码和验证码以及其自动生成的作为唯一身份验证的cookie作为参数POST到网站的服务器,进行身份验证,从而登录。(至于cookie和session的区别以及POST和GET的区别下文会作解释)参数如下图:

基于HttpClient的正方教务系统模拟登录(带验证码)_第5张图片

在用代码进行登录网站时,是需要将这些表单数据作为参数提交的,但是虽然有很多参数,但是真正需要的参数只需要其中标记的四个参数:

学号:txtUserName

密码:TextBox2

验证码:txtSecretCode

_VIEWSTATE,该参数其实对于相同学校的官网都是一样的,至于该参数在哪,在登录网页的html源码中:

基于HttpClient的正方教务系统模拟登录(带验证码)_第6张图片

好了,需要提交的参数都已经了解了,那么模拟登录就可以实现了,但是,现在问题来了,就是登录页面时需要提交验证码,那验证码哪里获取呢?如下图:

基于HttpClient的正方教务系统模拟登录(带验证码)_第7张图片

所以每一次提交信息的时候就需要get该网页获取验证码,因为对于验证码的自动识别比较麻烦,所以在这个项目中时将验证码获取下载到本地,再人工手动输入该验证码,进行登录。

【高能:关于验证码的自动识别,我在下一篇博文中会做出详细解释】

在成功登录后就可以利用cookie访问想访问的页面了。

基于HttpClient的正方教务系统模拟登录(带验证码)_第8张图片

总体框架已经大致熟悉,接下来就直接上源码:



package com.yxs.Link;  

import org.apache.commons.httpclient.*;  

import org.apache.commons.httpclient.methods.*;  

import org.apache.commons.httpclient.cookie.*;  

import java.util.*;  

import org.jsoup.*;  

import org.jsoup.nodes.*;  

import org.jsoup.select.*;  

import java.io.*;  

/**

 *

Tile:LoginTest.java

 *

Description:获取cookie和验证码模拟登录教务系统

 * @author YXS

 * @data2018年6月3日

 */  

public class LoginTMSystem {  


public static String getHTML() {  

String cookie1 ="";  

String html ="null";  

String txtSecretCode ="";  

System.out.println("请输入学号:");  

String txtUserName =new Scanner(System.in).next().trim();  

System.out.println("请输入密码:");  

String TextBox2 =new Scanner(System.in).next().trim();  

String RadioButtonList1 ="";  

String __VIEWSTATE ="";  


//登录url  

String loginURL ="http://jwxt.hfnu.edu.cn/(arezfqaeu12awkft0yuoe1qg)/default2.aspx";  

//教务管理系统首页url  

String indexURL ="http://jwxt.hfnu.edu.cn/(arezfqaeu12awkft0yuoe1qg)/xs_main.aspx";  

//登录后访问的课程表url  

String dataURL ="http://jwxt.hfnu.edu.cn/(arezfqaeu12awkft0yuoe1qg)/xskbcx.aspx";  

//验证码下载网址  

String checkCodeURL ="http://jwxt.hfnu.edu.cn/(arezfqaeu12awkft0yuoe1qg)/CheckCode.aspx";  

//创建浏览器对象  

HttpClient httpClient =new HttpClient();  


//先访问验证码页面,获取验证码  

GetMethod getMethod1 =new GetMethod(checkCodeURL);  

try {  

//设置HttpClient接收Cookie  

            httpClient.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);  

            httpClient.executeMethod(getMethod1);  

//获取访问后的Cookie  

            Cookie[] cookies1 = httpClient.getState().getCookies();  

StringBuffer tmpcookies1 =new StringBuffer();  

for(Cookie c1 : cookies1) {  

tmpcookies1.append(c1.toString()+";");  

System.out.println("访问页面cookies : "+c1.toString());  

                cookie1 = tmpcookies1.toString();  

//cookie1 = c1.toString();  

            }  


//验证码保存路径  

File storeFile =new File("D:\\java爬虫jar包\\verifycode\\vc.gif");  

            InputStream is = getMethod1.getResponseBodyAsStream();  

FileOutputStream fos =new FileOutputStream(storeFile);  

byte[] b = new byte[1024];  

int n;  

while((n = is.read(b)) != -1) {  

fos.write(b,0,n);  

            }  

            is.close();  

            fos.close();  


//该处代码是用于验证码自动识别  

txtSecretCode = ImagePreProcess.getAllOrc("D:\\java爬虫jar包\\verifycode\\vc.gif");  

}catch(Exception e) {  

            e.printStackTrace();  

        }  

//System.out.println("请输入验证码:");  

//String txtSecretCode = new Scanner(System.in).next().trim();  

        System.out.println(txtSecretCode);  

System.out.println("测试使用cookie1:"+cookie1);  


//模拟登录,按实际服务器端要求选用Post 或 Get请求方式  

PostMethod postMethod =new PostMethod(loginURL);  

//设置相同的cookie  

postMethod.setRequestHeader("cookie",cookie1);  

//设置登录时需要的信息,用户名和密码  

        NameValuePair[] data = {  

new NameValuePair("__VIEWSTATE",__VIEWSTATE),  

new NameValuePair("Button1",""),  

new NameValuePair("hidPdrs",""),  

new NameValuePair("hidsc",""),  

new NameValuePair("lbLanguage",""),  

new NameValuePair("RadioButtonList1",RadioButtonList1),  

new NameValuePair("TextBox2",TextBox2),  

new NameValuePair("txtSecretCode",txtSecretCode),  

new NameValuePair("txtUserName",txtUserName)  

        };  

        postMethod.setRequestBody(data);  

try {  

int statusCode = httpClient.executeMethod(postMethod);  

//html = postMethod.getResponseBodyAsString();  

            System.out.println(statusCode);  


//重定向  

if(statusCode == 302) {   

System.out.println("模拟登录成功!");  


//设置HttpClient接收Cookie,用与浏览器一样的策略  

                httpClient.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);  

//获取登录后的Cookie  

                Cookie[] cookies = httpClient.getState().getCookies();  

StringBuffer tmpcookies =new StringBuffer();  

for(Cookie c : cookies) {  

tmpcookies.append(c.toString()+";");  

System.out.println("登录页面cookies : "+c.toString());  

                }  

//System.out.println("-------------------------------InformationFrame------------------------------");  


//进行登录后的操作  


//访问主页面  

GetMethod getMethod2 =new GetMethod(indexURL+"?xh="+txtUserName);  

//每次访问需授权的网址时带上前面的cookie作为通行证  

getMethod2.setRequestHeader("cookie",tmpcookies.toString());  

                httpClient.executeMethod(getMethod2);  


//访问课程表页面  

GetMethod getMethod3 =new GetMethod(dataURL+"?xh="+txtUserName+"&xm=&gnmkdm=");  

//每次访问需授权的网址时带上前面的cookie作为通行证  

getMethod3.setRequestHeader("referer",indexURL+"?xh="+txtUserName);  

getMethod3.setRequestHeader("cookie",tmpcookies.toString());  

                httpClient.executeMethod(getMethod3);  

                html = getMethod3.getResponseBodyAsString();  


/*//访问主页面

                GetMethod getMethod = new GetMethod(indexURL+"?xh="+txtUserName);

                //每次访问需授权的网址时带上前面的cookie作为通行证

                getMethod.setRequestHeader("cookie",tmpcookies.toString());

                httpClient.executeMethod(getMethod);

                html = getMethod.getResponseBodyAsString();*/  

}else {  

System.out.println("登录失败");  

            }  

}catch(Exception e) {  

            e.printStackTrace();  

        }  

return html;  

    }  

public static void main(String[] args) {  

        Document doc = Jsoup.parse(LoginTMSystem.getHTML());  

//System.out.println(doc);  

/*Element link1 = doc.select("span#Label3").first();

        String text1 = link1.text();

        Element link2 = doc.select("span#xhxm").first();

        String text2 = link2.text();

        System.out.println(text1+text2);*/  

Elements link1 = doc.select("span#Label5");  

Elements link2 = doc.select("span#Label6");  

Elements link3 = doc.select("span#Label7");  

Elements link4 = doc.select("span#Label8");  

Elements link5 = doc.select("span#Label9");  

        String text1 = link1.text();  

        String text2 = link2.text();  

        String text3 = link3.text();  

        String text4 = link4.text();  

        String text5 = link5.text();  

System.out.println("-------------------------------个人信息------------------------------");  

        System.out.println(text1);  

        System.out.println(text2);  

        System.out.println(text3);  

        System.out.println(text4);  

        System.out.println(text5);  

System.out.println("-------------------------------课程信息------------------------------");  

Elements tds1 = doc.select("td[rowspan=2]");  

for(int i=0;i

String td = tds1.get(i).text().replace(Jsoup.parse(" ").text(), " ");  

            System.out.println(td);  

        }  

Elements tds2 = doc.select("td[rowspan=3]");  

for(int i=0;i

String td = tds2.get(i).text().replace(Jsoup.parse(" ").text(), " ");  

            System.out.println(td);  

        }  

System.exit(0);  

    } 



结果显示


基于HttpClient的正方教务系统模拟登录(带验证码)_第9张图片

源码的话,就自己解读吧,理解核心方法的话,就很容易理解代码的意思,而且其中也添加了许多注释,如果还有不懂得话,可以在评论里提问或者不嫌弃我菜的话加我qq1420755674进行交流。


【注1】cookie和session的缠绵悱恻


当你在浏览网站的时候,WEB

服务器会先送一些资料放在你的计算机上,Cookie会帮你在网站上所打的文字或是一些选择,都记录下来。当下次你再光临同一个网站,WEB

服务器会先看看有没有它上次留下的 Cookie 资料,有的话,就会依据Cookie里的内容来判断使用者,送出特定的网页内容给你。 Cookie

的使用很普遍,许多有提供个人化服务的网站,都是利用Cookie来辨认使用者,以方便送出使用者量身定做的内容,像是 Web 接口的免费 email

网站,都要用到 Cookie。


    具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。

同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的,但实际上它还有其他选择。

cookie机制。正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript或者VBScript也可以生成cookie。而cookie的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。



cookie的内容主要包括:名字,值,过期时间,路径和域。路径与域一起构成cookie的作用范围。若不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就消失。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。若设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式


    session机制。session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识(称为sessionid),如果已包含则说明以前已经为此客户端创建过session,服务器就按照sessionid把这个session检索出来使用(检索不到,会新建一个),如果客户端请求不包含sessionid,则为此客户端创建一个session并且生成一个与此session相关联的session id,sessionid的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。保存这个sessionid的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。一般这个cookie的名字都是类似于SEEESIONID。但cookie可以被人为的禁止,则必须有其他机制以便在cookie被禁止时仍然能够把sessionid传递回服务器。

    经常被使用的一种技术叫做URL重写,就是把sessionid直接附加在URL路径的后面。还有一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把sessionid传递回服务器。比如:


实际上这种技术可以简单的用对action应用URL重写来代替。

cookie 和session 的区别:

1、cookie数据存放在客户的浏览器上,session数据放在服务器上。

2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE操作,考虑到安全应当使用session。

3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用COOKIE。

4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

【注2】POST和GET的双宿双飞

从安全性看get

从提交的内容大小来看get

从请求响应速度来看get>post,get要求服务器立即处理请求,而post请求可能形成一个队列请求。


记其中的三两坑

【坑1】

在前期停滞了两天,就是关于那个登录界面的验证码和你访问验证码网页获得的验证码是否一致的问题,我当时一直搞不懂如何保持相同,也搜了很多但是都是没有说到点子上或者很多教程都是没有在该点上侧重注意的,所以这一点算是坑死我。下图是当时搜的一些讨论。后来还是经过学长的指点才算是醍醐灌顶。


基于HttpClient的正方教务系统模拟登录(带验证码)_第10张图片

先简单说说我当时的思考思路:由于我在POST参数时,需要将账号密码验证码和提交,这时是POST到loginURL,但是我有验证码的前提是要GET下checkCodeURL下载验证码,可是我怎么保证两者的验证码就是同一验证码呢?

所以我想先GETcheckCodeURL获取验证码,同时获取该网页的cookie,然后再用相同的cookie作为参数POST到loginURL,这样的意思就是保证让服务器知道我两次访问的是同一用户,以保证两个验证码保持一致,但是结果,当然毫无起色!!!!!那么问题出在那了?


基于HttpClient的正方教务系统模拟登录(带验证码)_第11张图片

看了这么多图片,你应该能够发现每个URL中都有和普通URL不同之处,如下图,URL中有一长串字符编码,【重点】其实这个就是对每个网页的一个唯一标识码,就是当不同次访问网页是服务器都会生成一个标识码,只有当这个标识码保持一致时,服务器才会认定两个验证码是保持一致的。


基于HttpClient的正方教务系统模拟登录(带验证码)_第12张图片

【坑2】

其实解决完坑1后,你就会发现可以成功登录了,但是这时当你想POST课程表页面时,会出错!WTF ?!


基于HttpClient的正方教务系统模拟登录(带验证码)_第13张图片

这时也是有三个参数要提交,和前面的类似,但是关键关键之处:一定要加该句,用于告诉该POST的网页你从哪里来。


基于HttpClient的正方教务系统模拟登录(带验证码)_第14张图片

你可能感兴趣的:(基于HttpClient的正方教务系统模拟登录(带验证码))