按F12开启浏览器抓包。
选择Network选项,并记得选上Preserve log选项,否则页面一旦跳转,之前抓的包就会消失。
通过对首页进行刷新抓包,我抓到了以下数据包。
通过对数据包的逐个观察,我选出了首页数据包里的三个关键包。
一个是登陆界面的数据包,里面包含了完整的登陆Form表。
根据登陆界面的代码分析得出表单的上传还需要一些加密方法,“加密算法数据包”
随后我们再对登陆过程进行抓包。一般登陆表都是采用POST的方式上传。(此时如果没有选上Preserve log选项,将观察不到登陆数据包)
由下图可知服务器的请求地址(Requesst URL)和请求方式(Request Method)
登陆数据包携带的数据(From Data)
在上面我们抓了四个数据包,分别是登陆界面包,加密算法包,验证码包,登陆包。现在进行详细分析。
登陆界面包的请求地址和请求方式
请求头
请求头里重要的参数有Cookie、Referer和User-Agent,其它参数可加可不加。
我们的最终目标就是要获取一个已登陆的Cookie。
Referer是一个服务器需要验证的参数,如果没有,服务器将返回“系统出错”等。
User-Agent是为了降低被后台识别为爬虫的几率。
根据登陆界面代码里的具体加密方法调用逻辑来使用。
请求地址和请求方式,可以注意到登陆包和登陆界面包的请求地址是一样的,但请求方式不同。
请求头
Form Data
结合登陆界面的代码分析可得下表
参数名 | 参数值来源 |
---|---|
__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();
}
}
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进行简单测试。
登陆界面,账号密码暂时内置。
登陆成功
成功获取到成绩图,没进行适配,因为只是测试。
在进行模拟登陆的过程中,我发现一旦程序出现BUG非常难以发现,因为服务器只会一直返回系统出错。所以,如果你一直失败,要重点检查请求头参数和表单数据。