Java爬虫爬取旧版正方教务系统课程表、成绩表
一、项目展示
1.正方教务系统
-
首页
2.爬虫系统
-
首页:
-
成绩查询:
-
课表查询:
二、项目实现
1.爬取思路描述
无论是成绩查询或课表查询亦或者其它的信息查询,都必须是要在登录状态下才能进行。而要登录教务系统,就要先获取登录的验证码,然后输入学号密码和验证码,向教务系统发起登录请求,登录成功后,需要保存登录状态,即记录cookie。有了登录成功后的cookie,就能对其他页面发起请求,旧版的正方系统返回的是Html,所以拿到请求结果后,还要再进行Html的解析,进而筛选出自己所需要的信息。
2.代码实现的总体思路
(1)爬虫、数据解析工具
- HttpClient:用于像浏览器发起http请求,支持长连接
- Jsoup:用于解析Html,支持DOM,CSS以及类似于jQuery的操作方法来取出和操作数据
- 正则表达式:按需求提取字符串的特定部分,也是用于解析Html
(2)项目框架
项目用的是Springboot
搭建项目,因为当时简单用Vue
搭了个前台,所以数据传输都是用的Json,实现了前后端的分离,主要是用到了Spring
的IoC容器管理bean还有控制器类。第三方依赖是用Maven
来管理。实际上,这个项目不一定要用SpringBoot,可以根据自己的需要进行迁移。代码包结构如下:
(3)核心类简介
GloabalConstant
类:全局常量类,存放了所有的请求URL,包括教务系统首页、登录请求地址、验证码请求地址等,这些URL需要根据自己的实际情况进行手动更改,把域名部分换成自己学校正方系统首页的地址就行。另外就是登录页的错误信息,为了方便调试代码,也进行了保存。HttpService
类:Http服务类,封装了get请求、post请求,以及HttpClient的初始化,同时所有关于爬取逻辑的代码都是在这个类里,包括登录、验证码获取与识别、课表表获取、成绩表获取等。JavaOCR
类:验证码识别类,包括验证码识别的整个过程,**由于验证码识别训练涉及到数据集、测试集、结果集,启动代码时,请根据自己的实际情况,在配置文件执行修改trainSetDir
、trainTestDir
、trainResultDir
这几个目录所在的位置。**验证码识别的训练与使用是分开的,项目运行时只会在HttpService
中读取训练结果集,如果要自己进行验证码的训练(理论上测试集验证码图片越多,识别率越高,我总共用了近700张,识别率稳定在62%左右),在src.test.java.*
下有代码示例。
(4)配置文件说明
配置文件用的是yml格式,application-dev.yml
是开发环境的配置文件,application-prod.yml
是生产环境(linux下)的配置文件,可以自定义端口以及JavaOCR目录。
(5)要注意的细节
-
在获取Cookies后,以后的每一次请求都要把Cookies带上。
-
请求时要注意目标请求是否需要Referer。Referer告诉服务器我是从哪个页面链接过来的,服务器基此可以获得一些信息用于处理,有网页会限定请求的上一个地址。
3.模拟登录
(1)分析登录页面
我用的Google Chorm,在首页按F12打开浏览器自带的页面审查工具,随便输入学号密码和验证码,点击登录后,浏览器会向服务器提交一个post请求,请求地址为:http://xxxxxxxxxx/default2.aspx。
仔细观察上面的Form Data表单,发现有以下几个关键表单项:
- __VIEWSTATE:一个隐藏表单项,可以在页面源码中找到
- txtUserName:学生学号
- TextBox2:登录密码
- txtSecretCode:验证码
- RadioButtonList1:结合登陆页面知,这是身份选项,value值为%D1%A7%C9%FA(”学生”经过以Gb2312格式URL编码后的字符串 )
其他像Textbox1、Button1这些表单项的value值都是空白的,说明在登录中并不起作用。
(2)登陆前的准备:获取cookie和__VIEWSTATE
获取到cookie和__VIEWSTATE后要进行保存,项目中是采用session的
方式,存放在服务器端,在之后的请求中,每次请求都要带上cookie,比如获取验证码。HttpService
类已经封装好了get请求和post请求,每次请求都会自动带上cookie。
/**
* 初始化,主要用于收集cookie和viewState
*/
public HttpBean init() {
CloseableHttpResponse requestResponse = sendGetRequest(GlobalConstant.INDEX_URL, "");
String cookie = requestResponse.getFirstHeader("Set-Cookie").getValue();// 获取cookie
HttpBean httpBean = new HttpBean();
try {
String html = EntityUtils.toString(requestResponse.getEntity(), "utf-8");
httpBean.setViewState(getViewState(html));//提取页面表单中的__VIEWSTATE的值
httpBean.setCookie(cookie);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("完成初始化,获取到的cookie为" + httpBean.getCookie()
+ ",获取到的viewState为" + httpBean.getViewState());
return httpBean;
}
/**
* @param html 登录页面源码
* @return 登录页的__VIEWSTATE
*/
public String getViewState(String html) {
return Jsoup.parse(html).select("input[name=__VIEWSTATE]").val();
}
(3)验证码的获取与自动识别
/**
* 获取验证码
*
* @return 验证码图片
*/
public byte[] getCheckImg() {
String url = GlobalConstant.SECRETCODE_URL;
byte[] imgByte = null;
try {
CloseableHttpResponse requestResponse = sendGetRequest(url