本次实例主要是对于接入青果系统的某高校教务系统进行期末成绩爬取,本次模拟学校为延安大学西安创新学院的教务系统,实现语言为java,前端框架采用jquery+layerui,后端框架为springboot + redis + mysql 图形验证码识别用的百度AI
注:切勿用入商业用途,否则后果自负
登录教务处系统
- 经过百度拿到该学校的教务处系统,即http://www.xacxxy.com:88/jwweb/
- 通过F12拿到该系统的前端渲染后的代码,即 view-source:http://www.xacxxy.com:88/jwweb/_data/index_LOGIN.aspx
- 找到输入用户名和密码的input框,然后解析和解密,从输入框可以看到输入的内容传到后台是通过一系列加密的,于是拿到其加密算法,
- 拿到这个,其实已经成功了一大半了,于是解析一下这一段内容
function chkpwd(obj) {
if (obj.value != '') {
//对学号进行md5加密并且转成大写 然后再拼接上该学校的代码
var s = md5(document.all.txt_asmcdefsddsd.value + md5(obj.value).substring(0, 30).toUpperCase() + '13683').substring(0, 30).toUpperCase();
document.all.dsdsdsdsdxcxdfgfg.value = s;
} else {
document.all.dsdsdsdsdxcxdfgfg.value = obj.value;
}
}
//处理验证码的js 同密码
function chkyzm(obj) {
if (obj.value != '') {
var s = md5(md5(obj.value.toUpperCase()).substring(0, 30).toUpperCase() + '13683').substring(0, 30).toUpperCase();
document.all.fgfggfdgtyuuyyuuckjg.value = s;
} else {
document.all.fgfggfdgtyuuyyuuckjg.value = obj.value.toUpperCase();
}
}
于是可以把这一段内容用java实现
String xuehao = MD5Encode(id +
MD5Encode(password).substring(0, 30).toUpperCase() + school).toUpperCase().substring(0, 30);
String yzm = md5(md5(validate.toUpperCase()).substring(0, 30).toUpperCase() + school)
.substring(0, 30).toUpperCase();
注:Md5加密算法百度一大堆,上述的id为 学号字段,password为前台传入的密码字段,school为学校的编号,validate为我们解析成功后的验证码
这里说到验证码,因为我们模拟登陆即要进行机器识别验证码,java的ocr我试过 识别率太低,于是接入了百度AI识别,虽然有次数限制,但是识别率还是比较高。
一番周折之后,可以进行框架搭建和逻辑编写了
识别验证码
我们用的python写的深度识别
这里暂不说这个,申请完之后会拿到一个appid和appkey,secretkey,后续验证码识别会用到
通过教务系统拿到登录系统的验证码地址
即验证码地址
从chrome打开会是一段乱码,不需要管。
我们直接开始模拟爬取该地址然后进行识别:
public static List getImage() {
//获取该地址
String url = validateUrl+System.currentTimeMillis()%1000;
try {
HttpResponse response = HttpUtil.createGet(url)
.header("Accept","image/webp,image/apng,image/*,*/*;q=0.8")
.header("Accept-Encoding", "gzip, deflate")
.header("Accept-Language","zh-CN,zh;q=0.9")
.header("Connection","keep-alive")
//host/refer 即该教务处的host ...因为是模拟登录,所以要模拟的像一些
.header("Host",Constant.HOST)
.header("Referer", Constant.LOGIN_URL)
.header("User-Agent",Constant.USER_AGENT)
.execute();
InputStream in = response.bodyStream();
long time = System.currentTimeMillis();
//这里拿到输入流之后进行读取 然后图片下载下来
String filePath = CommonLog.CACHE_MAP.get("filePath")+ time+".jpg";
OutputStream out = new FileOutputStream(filePath);
byte b[] = new byte[1024 * 8];
int len = 0;
while ((len = in.read(b)) != -1) {
out.write(b, 0, len);
}
System.out.println("1:>>>"+response.getCookieStr());
System.out.println(Base64Encoder.encode(filePath));
List list = Lists.newArrayList();
//下载下来之后 进行验证 即getValidate
list.add(response.getCookies().get(0));
String validateCode = getValidate(filePath,0);
list.add(validateCode);
return list;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
识别图片
public static String getValidate(String filePath,int j){
String url = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic";
byte[] imgData = new byte[0];
try {
imgData = FileUtil.readFileByBytes(filePath);
String imgStr = Base64Util.encode(imgData);
String imgParam = URLEncoder.encode(imgStr, "UTF-8");
String param = "&image=" + imgParam;
try {
String result = top.choviwu.toolkit.sort.util.HttpUtil.post(url, getAuth(), param);
JSONObject jsonObject = new JSONObject(result);
JSONArray jsonArray = jsonObject.getJSONArray("words_result");
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject json = jsonArray.getJSONObject(i);
stringBuilder.append(json.get("words"));
}
String ret = stringBuilder.toString();
// while ((StringUtils.isBlank(stringBuilder.toString()) || stringBuilder.toString().length()!=4)&& j <= 5) {
// getValidate(filePath, j++);
// }
return StringUtils.replace(ret, " ", "");
} catch (Exception e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
模拟登录
public static HttpCookie login(String id, String password,String school) {
//获取验证码
List list = ValidateImage.getImage();
HttpCookie cookie = ((HttpCookie) list.get(0));
String validate = (String) list.get(1);
String xuehao = MD5Encode(id + MD5Encode(password).substring(0, 30).toUpperCase() + school).toUpperCase().substring(0, 30);
String yzm = md5(md5(validate.toUpperCase()).substring(0, 30).toUpperCase() + school).substring(0, 30).toUpperCase();
HttpResponse response = HttpUtil.createPost(url).form(map)
.form("dsdsdsdsdxcxdfgfg", xuehao)
.form("fgfggfdgtyuuyyuuckjg", yzm)
.form("__VIEWSTATE", getViewState())
.form("txt_asmcdefsddsd", id)
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Host", "www.xacxxy.com:88")
.header("Origin", "http://www.xacxxy.com:88")
.header("Referer", "http://www.xacxxy.com:88/jwweb/_data/index_LOGIN.aspx")
.header("Upgrade-Insecure-Requests", "1")
.header("User-Agent", Constant.USER_AGENT)
.header("Cookie", cookie.toString())
.execute();
//学号密码加密 然后再通过表单提交的方式提交 得到相应数据
String body = response.body();
if (body.contains(Constant.VALIDATE_ERROR)) {
login(id, password,school);
} else if (body.contains("帐号或密码不正确")) {
throw new CrudException(ExceptionEnum.account_or_password_error);
}
System.out.println("2:>>>" + cookie);
//成功返回cookie
return cookie;
}
登录成功之后就好办了,直接可以获取该成绩
拿到该成绩的接口地址 jwweb/xscj/Stu_MyScore_rpt.aspx
可以看出来这个jsp是成绩的渲染师徒视图,后面获取到成绩之和就解析该table
这里我们拿成绩分布的接口,为什么呢?因为查看成绩的接口后台是返回一张图片的,这样的话又要二次解析图片,反而很麻烦,这个接口可以直接通过table的解析来获取结果
于是开始爬该地址,
爬成绩接口
拿到上一个的cookie数据,然后请求上面说的那个接口
说一下这里面的参数,这里面需要传学期和学年的参数,
即2020学年第一学期传202001 学年则传2020
public static List<CourseScore> getCourseScores(HttpCookie cookies, String xnxq, String xn) {
List<CourseScore> courseScores = Lists.newArrayList();
try {
//上面传个20201 这里会解析学期的参数的
String xq = xnxq.substring(xnxq.length() - 1);
HttpResponse response = HttpUtil.createPost("http://host/jwweb/xscj/Stu_cjfb_rpt.aspx")
.header("Accept", "image/webp,image/apng,image/*,*/*;q=0.8")
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Host", "host")
.header("Referer", "http://host/jwweb/xscj/Stu_cjfb.aspx")
.header("Upgrade-Insecure-Requests", "1")
.header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36")
.header("Cookie", cookies.toString())
.cookie(cookies.toString())
.form("sel_xn", xn).form("sel_xq", xq)
.form("SelXNXQ", Integer.valueOf(xq) + 1)
.form("submit","")
.execute();
System.out.println(response.body());
//拿到数据进行解析table元素,遍历成绩
Elements elements = Jsoup.parse(response.body()).children();
Elements scores = elements.get(0).children().get(1).children().get(0).children().get(1).getElementsByTag("tr");
for (int i = 0; i < scores.size(); i++) {
if(i>=scores.size()-2){
return courseScores;
}
Elements childrens = scores.get(i).children();
String course = childrens.get(1).text();
String score = childrens.last().text();
CourseScore courseScore = new CourseScore();
courseScore.setAddtime(new BigDecimal(System.currentTimeMillis()))
.setCourseCredit(Double.valueOf(childrens.get(2).text()))
.setCourseName(course)
.setCourseType(childrens.get(3).text())
.setNatureRead(childrens.get(5).text())
.setScore(Double.valueOf(score))
.setTestType(childrens.get(4).text())
.setXnxq(xnxq);
courseScores.add(courseScore);
}
return courseScores;
}catch (Exception e){
e.printStackTrace();
}finally {
return courseScores;
}
}
上面为后端的逻辑
前端逻辑就不展示了,都是一些html
主要是说了一下这个的思想
登录完成之后会重定向到获取成绩页,这里其实是绕过了验证码登录的哪一步
这里我统一返回了Json结构,方便解析
到这里就贴完了,如果有看不懂的可以私聊我QQ:QQ
或者我的博客博客
代码我丢到了github上,需要的可自行去debug 仓库
clone下代码之后需要切换到dev分支
https://github.com/planetRun/qingguo-spider
本篇为原创,转载须注明来源,否则后果自负