本案例适用于基于 新版“湖南强智科技” 的教务系统(全国200多所高校),实践于“南昌大学网络教务系统” http://218.64.56.18/xk/。
新版教务系统终于上线了,终于修复了古老浏览器才能登录使用的老毛病,由于自己正在做一款Android校园客户端,需要实现“一键查成绩”,遂开始了对教务系统的抓取。
教务系统属于登录类型,需要查看后台post数据。
工具:Fiddler
设定为教务系统的host:218.64.56.18,这个地址不同的学校不同
可以看到请求中post方式提交了username和password,使用了session来保存会话。 并没有马上返回网页,而是在response中使用了302重定向,同时将session传递过去,从此,该用户的会话建立,用这个session走通全站!看来新版的教务系统比老版的还简单!
直接进入查看成绩模块,选择学期: 2015-2016-1,点击查询,成绩就在网页上显示了,现在来看数据。
这里直接定位到了 http://218.64.56.18/kscj/cjcx_list
,这个地址就是最终成绩呈现的地址。
# request:
1. 提交了session,这个就是之前的session
2. 提交了 kksj(开课时间)、kcxz(课程性质)、kcmc(课程名称)、xsfs(显示方式),竟然用拼音,太low了
# response:
1. 返回了200 ok, 也就是下面body里的内容就是最终包含成绩的html源代码了,去解析吧
我是用了Bmob的后端云,只支持nodejs,所以使用了nodejs。
var req = require('request'); // http请求库
var htmlparser = require('htmlparser'); // html解析库
var kksj = '2015-2016-1'; // 开课时间
var username = '8000113237'; // 学号
var password = 'xxxxxxxx'; // 密码
var loginUrl = 'http://218.64.56.18/xk/LoginToXk'; // 登录地址
var scoreUrl = 'http://218.64.56.18/kscj/cjcx_list'; // 成绩地址
//发起POST请求
req.post({url:loginUrl, form:{USERNAME:username, PASSWORD: password}}, function(err, res, body) {
console.log(res.headers);
}
// 得到的结果:
F:\Code\nodejs>node getScore.js
{
server: 'Apache-Coyote/1.1',
'set-cookie': [ 'JSESSIONID=B0C7F4EE40AEA1DE1500F1548B44DC62; Path=/' ],
location: 'http://218.64.56.18/framework/xsMain.jsp',
'content-length': '0',
date: 'Wed, 06 Apr 2016 09:09:11 GMT',
connection: 'close'
}
// 在http响应header中,得到了cookie里面包含的session,接下来我们再带着这个session去访问成绩页面
// 返回的cookie包含了session
var set_cookie = res.headers['set-cookie'];
// 配置请求参数
var options = {
url : scoreUrl,
headers: {
'cookie': set_cookie
},
form: { /* 提交查询条件 */
'kksj': kksj,
'kcxz': '',
'kcmc': '',
'xsfs': 'all'
}
};
// 发送post请求
req.post(options, function(err,res,body){
console.log(body);
}
// 这里返回的是html源码,比较长,就不展示了,我们的成绩保存在 table 中
// 由于页面只有一个 table,我们可以把table部分解析出来,也可以直接取 td 内容
// 这里使用 htmlparser 库
我是用的是 htmlparser 库,一个可以通过 id、name、class、type等多种方式匹配的解析库,这个库现在出了 htmlparser2 ,更多去看官方文档吧 htmlparser
// 解析html, body即之前获取到的网页源代码
var rawHtml = body;
var handler = new htmlparser.DefaultHandler(function (err, dom) {
}, { verbose: false });
var parser = new htmlparser.Parser(handler);
parser.parseComplete(rawHtml);
// 获取姓名
var loginName = htmlparser.DomUtils.getElementById('Top1_divLoginName',handler.dom)
// 将获取到的的集合转化为json输出,这里的2只用两个空格缩进输出
console.log(JSON.stringify(loginName, null, 2));
执行后就获得了下面的内容:
{
"type": "tag",
"name": "div",
"attribs": {
"id": "Top1_divLoginName",
"class": "Nsb_top_menu_nc",
"style": "color: #000000;"
},
"children": [
{
"data": "汪鹏(8000113237)",
"type": "text"
}
]
}
到这里,姓名学号就获得到了,直接通过loginName['children']['data']
就可获得了。
课程成绩信息也可以通过同样的方法来解析完成!
getScore.js
/**
* 抓取强智新教务系统成绩
* Created by chiyiw on 04/04/16.
*/
var req = require('request'); // http请求库
var htmlparser = require('htmlparser'); // html解析库
var kksj = '2015-2016-1'; // 开课时间
var username = '8000113237'; // 用户名
var password = 'xxxxxxxx'; // 密码
var loginUrl = 'http://218.64.56.18/xk/LoginToXk'; // 登录地址
var scoreUrl = 'http://218.64.56.18/kscj/cjcx_list'; // 成绩地址
//发起POST请求
req.post({url:loginUrl, form:{USERNAME:username, PASSWORD: password}}, function(err, res, body) {
// 返回的cookie,里面包含了session
var set_cookie = res.headers['set-cookie'];
// 配置请求参数
var options = {
url : scoreUrl,
headers: {
'cookie': set_cookie
},
form: {
'kksj': kksj,
'kcxz': '',
'kcmc': '',
'xsfs': 'all'
}
};
// 发送post请求
req.post(options, function(err,res,body){
// 解析html
var rawHtml = body;
var handler = new htmlparser.DefaultHandler(function (err, dom) {
}, { verbose: false });
var parser = new htmlparser.Parser(handler);
parser.parseComplete(rawHtml);
var inputs = htmlparser.DomUtils.getElementsByTagName('input',handler.dom);
// 如果密码错误会跳转到登录界面,这个界面有多个input标签
if (inputs.length > 2){
response.send('{"name":"用户名或密码错误","term":"","score":[]}');
return;
}
// 获取姓名
var loginName = htmlparser.DomUtils.getElementById('Top1_divLoginName',handler.dom)['children'][0]['data'];
// 查询到所有的td标签
var tds = htmlparser.DomUtils.getElementsByTagName('td', handler.dom);
var datas = htmlparser.DomUtils.getElementsByTagType('text', tds);
// 获取学期
var term = datas[1]['data'];
// 添加成绩到数组中
var score = new Array();
for (var i = 0; i < datas.length; i++){
if (i%10 == 0){
var group = new Object();
group.course = datas[i+3]['data'];
group.score = datas[i+4]['data'];
group.credit = datas[i+5]['data'];
group.type = datas[i+9]['data'];
score.push(group);
}
}
// 输出JSONObject
var result = new Object();
result.name = loginName;
result.term = term;
result.score = score;
// 转化为字符串输出
console.log(JSON.stringify(result,null,2));
});
});