1. 1. 获取查询入口
  2. 2. 教材信息的获取
  3. 3. 查询结果的呈现

在大学的教务系统上,除了上一篇提到的教学评价,每学期还有一些挺烦人的事—比如选课、预订教材等。我觉得我们学校做得比较好的一点是可以在教务系统上自己选择是否要从学校订每学期的教材,不用被强制花冤枉钱买些一年也水分太多、基本看不了几次、扔掉时来还是全新的书(当然嘛,默认还是给你全部预订了的)。

但麻烦事也来了,学校教务系统那个查看教材具体信息的接口藏的老深了,要连着点几个弹窗中的链接才能到达,而且这种蛋疼的访问方式其中某个弹窗还要加载好久,稍没耐心你都没机会见到最后那个链接;更蛋疼的是你的订书情况和教材的具体信息根本就不在一处,说白了,教务系统本来就没想为学生自选教材服务嘛,谁管你麻不麻烦╮(╯▽╰)╭。

于是如果勤俭节约、热爱环保的好学生你退了书,但不知道怎么获得教材的具体信息,光从订书情况那寥寥几个并不总是准确的书名,来“揣测”你要自己买什么书,不仅麻烦,还有买错书的风险。所以,能方便随时查看要自购的教材信息和教学评价一样也是有着自动化需求的。

获取查询入口

说干就干,先来分析一下~学校的存的那些教材信息一定是常年存在而且可查询到的,所以那个套得最深的查询入口必然是随时可用,只要能稳定获得其中数据,再获取每个学生的教材预定情况就可以实现上述功能。果不其然,通过那个现在仅知的教材信息入口网上选课->学生选课->点击课程->点击教材,获知查询URL的格式如下,

http://教务系统地址/jcxx.aspx?xh=学号&xkkh=选课课号(先猜着,正方这无处不在的拼音也是醉了)

学号容易设置,那后面那个“选课课号”怎么获取呢?一步步获取弹窗里内容吗?这样做实在太麻烦,首先这个查询入口来自选修课界面,无法获知必修课的情况,再者只要这中间一处卡壳整个体验就很糟糕,最后这一路走来所有的“选课课号”都给加密了,根本看不出来是什么。于是再经过一番曲折查找,终于在信息查询->学生选课情况查询里获得了所有必要的信息,这里除了要修的课程齐全、还提供了所有选课课号(猜对了~)不用再去面对一个个弹窗和想着解密了,一举解决了上面三个问题;此外,这里还有每个课程的教材预定情况,所以只要将本页的信息进行重组就可实现查询所有要自购的教材了。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
var href = window.location.href,
url = href.match(/http:\/\/.+\//)[0],
sid = href.match(/xh=(.+)($|#|&|\/)/)[1],
classes = document.getElementById('iframeautoheight').contentDocument.getElementsByTagName('tr'),
cid = [],
className = [];
//获取没预定教材的课程号和课程名
for (var j = 1; j < classes.length; j++) {
if (classes[j].children[10].innerText == '0') {
cid.push(classes[j].children[0].innerText.trim());
className.push(classes[j].children[2].innerText.trim());
}
}

教材信息的获取

现在能查询了,那如何抓取查询所得信息呢?这里又有两个问题,首先用于查询的URL和教务系统主主体的URL是不一样的,存在跨域问题;第二,直接访问查询URL时,正方又来恶心一下你,整个页面一直刷新各不停,除了手动停止加载就没办法获取内容,难道有防抓取措施?

要解决第一个问题可以在系统的主体页面新建iframe、再在里面加载查询结果,然后就可以和一键教学评价一样通过iframe的contentDocument属性获取查询结果的文档内容(其实正方的大部分功能展现都是这么做的,但剩下那些就用的弹窗来恶心你了)。至于第二个问题,我回去比较了一下最开始通过一步步弹窗获得正常加载的情况,发现只要将加载的窗口或者iframe命名为‘jcxx’就能正常加载,而少了这一步就会导致页面疯狂刷新。所用代码如下:

1
2
3
4
5
6
7
//用内嵌框架将跨域的教材信息拉到同一页面下
var headDiv = document.getElementById('headDiv');
var newIframe = document.createElement('iframe');
newIframe.name = 'jcxx'; //没设置框架名会导致一直刷新
newIframe.src = url + 'jcxx.aspx?xkkh=' + cid[0] + '&xh=' + sid;
newIframe.style.display = 'none';
headDiv.appendChild(newIframe);

查询结果的呈现

获取了所有要自购的教材的查询URL、并且能够将查询结果呈现在本页之后,接下来的操作就可以和一键教学评价一样,利用加个周期操作对iframe里的内容进行逐步更新和获取即可。

至于获取的结果如何呈现,因为浏览器上不能对本地文件进行I/O操作,我一开始只是将所有信息塞在一个p标签里展现,但这样实在不优雅。结果查找一番,发现一个曲线救国方案:

HTML5中给a标签增加了一个download属性,只要有这个属性,点击这个链接时浏览器就不再打开链接指向的文件,而是改为下载。而下载文件的内容可以通过将a标签的href属性设为DataURI来写入。

ok,现在我们用下面函数就可将链接改为下载链接,并将获得的我们获得的查询结果全部写入一个text/plain类型的DataURI中,点击即可下载一个含有该DataURI内容的txt文件了。

1
2
3
4
function downloadFile(aLink, fileName, content){
aLink.download = fileName;
aLink.href = "data:text/plain," + content;
}

虽然能下载查询结果了,但下载下来的文件中却发现一个问题:传给DataURI的’\n’、’\t’等没发挥作用,排版全乱了。而之所以有这个问题,是因为作为查询结果的字符串必须进行编码才能传输特殊字符给URI,所以上述函数改为如下即可:

1
2
3
4
function downloadFile(aLink, fileName, content) {
aLink.download = fileName;
aLink.href = "data:text/plain," + encodeURIComponent(content);
}

现在,整个程序就大功告成了,完整的代码我已放github了。用法和一键教学评价一样,你可以在信息查询->学生选课情况查询里调出控制台执行它;也可将javascript: (function() {完整的程序})()保存为书签,在同一页面点击该书签即可运行脚本。目前在chrome下亲测有效,其他浏览器并不保证兼容^_^。