长沙理工大学教务管理系统模拟登陆

长沙理工大学教务管理系统模拟登陆

  • 准备
      • 浏览器抓包设置
      • 开始抓包
      • 开始数据包分析
        • 登陆界面包
        • 加密算法包
        • 验证码包
        • 登陆包
  • 代码实现
      • 密码加密
      • 验证码加密
      • 获取新的Cookie和必要的参数
      • 获取验证码
      • 开始模拟登陆
      • 判断是否登陆成功
  • 进一步应用
      • 获取成绩
      • 简单测试
  • 一点建议

准备

浏览器抓包设置

按F12开启浏览器抓包。
长沙理工大学教务管理系统模拟登陆_第1张图片
选择Network选项,并记得选上Preserve log选项,否则页面一旦跳转,之前抓的包就会消失。

开始抓包

通过对首页进行刷新抓包,我抓到了以下数据包。
长沙理工大学教务管理系统模拟登陆_第2张图片
通过对数据包的逐个观察,我选出了首页数据包里的三个关键包。

一个是登陆界面的数据包,里面包含了完整的登陆Form表
长沙理工大学教务管理系统模拟登陆_第3张图片
根据登陆界面的代码分析得出表单的上传还需要一些加密方法,“加密算法数据包
长沙理工大学教务管理系统模拟登陆_第4张图片

一个是验证码的数据包。
验证码数据包

随后我们再对登陆过程进行抓包。一般登陆表都是采用POST的方式上传。(此时如果没有选上Preserve log选项,将观察不到登陆数据包)
由下图可知服务器的请求地址(Requesst URL)和请求方式(Request Method)
长沙理工大学教务管理系统模拟登陆_第5张图片
登陆数据包携带的数据(From Data)
长沙理工大学教务管理系统模拟登陆_第6张图片

开始数据包分析

在上面我们抓了四个数据包,分别是登陆界面包,加密算法包,验证码包,登陆包。现在进行详细分析。

登陆界面包

登陆界面包的请求地址请求方式
登陆界面包的请求地址和请求方式
请求头
长沙理工大学教务管理系统模拟登陆_第7张图片
请求头里重要的参数有CookieRefererUser-Agent,其它参数可加可不加。
我们的最终目标就是要获取一个已登陆的Cookie
Referer是一个服务器需要验证的参数,如果没有,服务器将返回“系统出错”等。
User-Agent是为了降低被后台识别为爬虫的几率。

加密算法包

根据登陆界面代码里的具体加密方法调用逻辑来使用。

验证码包

请求地址请求方式
验证码
请求头
长沙理工大学教务管理系统模拟登陆_第8张图片

登陆包

请求地址请求方式,可以注意到登陆包和登陆界面包的请求地址是一样的,但请求方式不同
登陆包
请求头
长沙理工大学教务管理系统模拟登陆_第9张图片
Form Data
长沙理工大学教务管理系统模拟登陆_第10张图片
结合登陆界面的代码分析可得下表

参数名 参数值来源
__VIEWSTATE 登陆界面代码中匹配,会变化
__VIEWSTATEGENERATOR 登陆界面代码中匹配,会变化
pcInfo 直接复制包数据即可
txt_mm_expression 字符串空值""
txt_mm_length 字符串空值""
txt_mm_userzh 字符串空值""
typeName 点击From Data旁的view URL encoded可得值"%D1%A7%C9%FA" ,使用gb2312进行UrlDecode解码可得知该值实际为"学生"
dsdsdsdsdxcxdfgfg 加密后的密码,具体加密方式可以在登陆界面代码中看到,使用的加密算法在首页抓包中的md5.js中
fgfggfdgtyuuyyuuckjg 加密后的验证码,具体加密方式可以在登陆界面代码中看到,使用的加密算法在首页抓包中的md5.js中
Sel_Type 可以从登陆界面代码的登录表得知有多个值可选,一般为STU
txt_asmcdefsddsd 学号
txt_pewerwedsdfsdff 字符串空值""
txt_psasas 同typeName可得值为“请输入密码”
txt_sdertfgsadscxcadsads 字符串空值""

代码实现

接下来是具体的代码实现,我选择使用Java和Jsoup来构造。模拟登陆实质是模拟数据包发送接收和处理,使用哪种语言看个人爱好。

    public static Map<String, String> cookies = null;//用来存放Cookie
    public static final String validateCodeUrl = "http://xk.csust.edu.cn/sys/ValidateCode.aspx";//验证码请求地址
    public static final String pcinfo = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36undefined5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36 SN:NULL";
    public static final String loginPanelUrl = "http://xk.csust.edu.cn/_data/login_home.aspx";//登陆界面请求地址
    public static Map<String, String> datas = new HashMap<String, String>();//要Post的数据

密码加密

    public static String getPassword(String username, String password) {
    	ScriptEngineManager manager = new ScriptEngineManager();//Java自带可以解析Js文件的类
    	ScriptEngine engine = manager.getEngineByName("javascript");
    	try {
    		File file = new File("E:/jee-2019-032/work-space/hhh/src/hhh/md5.js");
    		// 读取js
    		FileReader fileReader = new FileReader(file);
    		// 执行指定脚本
    		engine.eval(fileReader);
    		if (engine instanceof Invocable) {
    			Invocable in = (Invocable) engine;
    			return (in.invokeFunction("md5", username
    					+ in.invokeFunction("md5", password).toString().substring(0, 30).toUpperCase() + "10536")
    					.toString().substring(0, 30));
    		}
    	} catch (Exception e) {
    		// TODO: handle exception
    		e.printStackTrace();
    	}
    	return null;
    }

加密逻辑来自登陆界面的代码

    function chkpwd(obj) {
    			if (obj.value != '') {
    				var s = md5(document.all.txt_asmcdefsddsd.value + md5(obj.value).substring(0, 30).toUpperCase() + '10536')
    					.substring(0, 30).toUpperCase();//具体加密逻辑
    				document.all.dsdsdsdsdxcxdfgfg.value = s;
    			} else {
    				document.all.dsdsdsdsdxcxdfgfg.value = obj.value;
    			}
    			chkLxstr(obj.value);
    		}

验证码加密

	public static String getValidateCode(String validateCode) {
		ScriptEngineManager manager = new ScriptEngineManager();
		ScriptEngine engine = manager.getEngineByName("javascript");
		try {
			File file = new File("E:/jee-2019-032/work-space/hhh/src/hhh/md5.js");
			// 读取js
			FileReader fileReader = new FileReader(file);
			// 执行指定脚本
			engine.eval(fileReader);
			if (engine instanceof Invocable) {
				Invocable in = (Invocable) engine;
				return in.invokeFunction("md5",
						in.invokeFunction("md5", validateCode.toUpperCase()).toString().substring(0, 30) + "10536")
						.toString().substring(0, 30).toUpperCase();
			}
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return null;
	}

加密逻辑来自登陆界面代码

		function chkyzm(obj) {
			if (obj.value != '') {
				var s = md5(md5(obj.value.toUpperCase()).substring(0, 30).toUpperCase() + '10536').substring(0, 30)
					.toUpperCase();//加密具体逻辑
				document.all.fgfggfdgtyuuyyuuckjg.value = s;
			} else {
				document.all.fgfggfdgtyuuyyuuckjg.value = obj.value.toUpperCase();
			}
		}

获取新的Cookie和必要的参数

    Connection con2 = Jsoup.connect(loginPanelUrl);
    			con2.header("Referer", "http://xk.csust.edu.cn/home.aspx");
    			con2.header("Upgrade-Insecure-Requests", "1");//不知道具体作用,为防止出错,带上为妙
    			con2.header("User-Agent", pcinfo);
    			Response rs2 = con2.ignoreContentType(true).execute();//没携带Cookie进行访问,服务器会返回新的Cookie
    			cookies = rs2.cookies();//获取新的Cookies!!!!!!!!!!!!
    			Document d2 = Jsoup.parse(rs2.body());
    			Element logonEelemt = d2.getElementById("Logon");//登陆界面代码的Form表单的Id是:Logon
    
    			for (Element e : logonEelemt.getAllElements()) {
    				if (e.attr("name").equals("__VIEWSTATE")) {//获取__VIEWSTATE
    					datas.put("__VIEWSTATE", e.val());
    				}
    				if (e.attr("name").equals("__VIEWSTATEGENERATOR")) {//获取__VIEWSTATEGENERATOR
    					datas.put("__VIEWSTATEGENERATOR", e.val());
    				}
    			}
    			datas.put("txt_asmcdefsddsd", "201716080227");// 学号
    			datas.put("dsdsdsdsdxcxdfgfg", getPassword("201716080227", "a8657090"));// getPassword方法获得加密后的密码
    			datas.put("Sel_Type", "STU");
    			datas.put("pcInfo", pcinfo);
    			datas.put("typeName", "学生");
    			datas.put("txt_psasas", "请输入密码");
    			datas.put("txt_mm_expression", "");
    			datas.put("txt_mm_length", "");
    			datas.put("txt_mm_userzh", "");
    			datas.put("txt_pewerwedsdfsdff", "");
    			datas.put("txt_sdertfgsadscxcadsads", "");

通过上面的代码,登陆POST的表单数据便只差加密后的验证码了。

获取验证码

			Connection connection = Jsoup.connect(validateCodeUrl);
			connection.header("Referer",
					"http://xk.csust.edu.cn/_data/login_home.aspx");
			connection.header("User-Agent", pcinfo);
			Response res = connection.ignoreContentType(true).cookies(cookies).execute();//必须带上cookies,否则验证码图片将和登陆cookie不匹配
			return res.bodyAsBytes();//将验证码图片转化为byte[]方便在GUI代码上展示

开始模拟登陆

	public static boolean startLogin(String validate) {
		try {
			// 登陆
			datas.put("fgfggfdgtyuuyyuuckjg", getValidateCode(validate));// 获取加密后的验证码
			Connection connect = Jsoup.connect("http://xk.csust.edu.cn/_data/login_home.aspx");
			connect.header("Referer", "http://xk.csust.edu.cn/_data/login_home.aspx");
			connect.header("Upgrade-Insecure-Requests", "1");
			connect.header("User-Agent", pcinfo);
			Response login = connect.ignoreContentType(true).data(datas).method(Method.POST).cookies(cookies).execute();
			boolean success = isLogin(login.body());//判断登陆是否成功
			return success;
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return false;
	}

判断是否登陆成功

POST登陆表单,服务器会返回结果代码。结果代码里包括但不限于“系统错误”、“验证码错误”以及“账号或密码错误”等字段。于是判断结果中包含了哪些字段便成了判断是否登陆成功的方法之一。

    public static boolean isLogin(String body) {
    	if(body.contains("正在加载")) return true;
    	else return false;
    }

进一步应用

至此,我们已经的得到了登录后的Cookie,使用该Cookie可以进行一些功能开发,比如课程查询、成绩查询或者抢课脚本等功能的开发。

获取成绩

通过抓包得到获取成绩的链接,POST后会返回成绩图和一些其他数据。这里只需要成绩图。

    public static String postGradeSearchOrder() {
    	Connection connection = Jsoup.connect("http://xk.csust.edu.cn/xscj/Stu_MyScore_rpt.aspx");
    	connection.header("Referer", "http://xk.csust.edu.cn/xscj/Stu_MyScore.aspx");
    	connection.header("Upgrade-Insecure-Requests", "1");
    	connection.header("User-Agent",pcinfo);
    	Map<String, String> dates = new HashMap<String, String>();
    	dates.put("sel_xn", "2018");//学年 2018-2019
    	dates.put("sel_xq", "1");//第一学期:0        第二学期:1   
    	dates.put("SJ", "0");    //原始成绩:0        有效成绩:1
    	dates.put("SelXNXQ", "2");//入学以来:0   学年:1  学期:2
    	dates.put("zfx_flag", "0");//主修:0   辅修:1
    	dates.put("btn_search", "检索");
    	dates.put("zxf", "0");//hidden
    	try {
    		Response response = connection.ignoreContentType(true).data(dates).method(Method.POST).cookies(cookies).execute();
    		Document document = Jsoup.parse(response.body());
    		Element element = document.getElementsByTag("img").first();//返回的代码里包含了成绩图链接
    		return element.attr("src");//返回成绩图片的链接
    	} catch (Exception e) {
    		// TODO: handle exception
    		e.printStackTrace();
    	}
    	return null;
    }

得到图片链接后进行进一步获取

    public static byte[] getGradeImage(String path) {
    	Connection connection = Jsoup.connect(path);
    	connection.header("Referer",
    			"http://xk.csust.edu.cn/xscj/Stu_MyScore_rpt.aspx");
    	connection.header("User-Agent",pcinfo);
    	try {
    		Response response = connection.cookies(cookies).method(Method.GET).ignoreContentType(true).execute();
    		return response.bodyAsBytes();//转换为byte[]方便GUI显示
    	} catch (Exception e) {
    		// TODO: handle exception
    		e.printStackTrace();
    	}
    	return null;
    }

简单测试

使用Java Swing进行简单测试。

登陆界面,账号密码暂时内置。
长沙理工大学教务管理系统模拟登陆_第11张图片
登陆成功
长沙理工大学教务管理系统模拟登陆_第12张图片
成功获取到成绩图,没进行适配,因为只是测试。
长沙理工大学教务管理系统模拟登陆_第13张图片

一点建议

在进行模拟登陆的过程中,我发现一旦程序出现BUG非常难以发现,因为服务器只会一直返回系统出错。所以,如果你一直失败,要重点检查请求头参数和表单数据。

你可能感兴趣的:(爬虫)