<小爱同学课程表>
-————河南理工大学教务系统(树维教务系统)适配
小爱同学课程表的教务系统导入是在小爱同学3.0推出的,也就是去年的9月份左右。给大学生的生活提供了很大的遍历,但是就搜索了一下我们学校的教务处,并没有适配,直到今年假期,绝对要尝试去适配。开始了简单的js学习,加之大一的时候就开始学习python爬虫,学习js就容易很多(并且在学习过程中发现js的语法有很多不是和c一样就是和python一样),而且这个适配也算是对课程表的爬取。下面就简单分享一下整个编写的流程。开头先介绍一下小爱同学的课程表,非常的方便,他不仅局限于MIUI系统,其他系统也可以在应用商店下载小爱同学或者单独下载小爱课程表。
本次的代码已开放到github,大家可以下载:
奥利给
废话少说,直接进入主题,本次将通过以下几个步骤去论述:
目录:
0:项目设计流程及代码参考
1:provide函数编写以及其他案例
2:paser函数编写以及调试
3:上传以及E2E测试
0_0:相关文档及代码参考:
官方文档飞机票:https://ldtu0m3md0.feishu.cn/docs/doccnhZPl8KnswEthRXUz8ivnhb
github代码链接:https://github.com/https-github-com-hpu-ylx/AIschedu-HPU_schedule.git
本文参考博客: 二氢茉莉酮酸甲酯
0_1:设计流程
1_0:先运行官方的provide函数
provider函数的作用就是以html文档的形式返回网页内容,供parser函数调用
function scheduleHtmlProvider(iframeContent = "", frameContent = "", dom = document) {
//除函数名外都可编辑
//以下为示例,您可以完全重写或在此基础上更改
const ifrs = dom.getElementsByTagName("iframe");
const frs = dom.getElementsByTagName("frame");
if (ifrs.length) {
for (let i = 0; i < ifrs.length; i++) {
const dom = ifrs[i].contentWindow.document;
iframeContent += scheduleHtmlProvider(iframeContent, frameContent, dom);
}
}
if (frs.length) {
for (let i = 0; i < frs.length; i++) {
const dom = frs[i].contentDocument.body.parentElement;
frameContent += scheduleHtmlProvider(iframeContent, frameContent, dom);
}
}
if(!ifrs.length && !frs.length){
return dom.querySelector('body').outerHTML
}
return dom.getElementsByTagName('html')[0].innerHTML + iframeContent+frameContent
}
1_1:HTTP请求
因为HPU的教务系统可以用官方的DOM方法,所以重点将如何用http方式获取,当然这也是我最后无意中尝试才发现的,在这里先说一下我之前的provider函数是怎么写的:
刚开始因为对js的爬取不了解,所以只能参考其他人的代码他是用的http方式获取的:
1 function scheduleHtmlProvider(iframeContent = "", frameContent = "", dom = document) {
2 let http = new XMLHttpRequest()
3 http.open('GET', '/jsxsd/xskb/xskb_list.do?Ves632DSdyV=NEW_XSD_PYGL', false)
4 http.send()
5 return http.responseText
6 }
通过http方式获取网页内容并返回,因为python的爬虫获取网页是用的request函数,并且伪装headers,如今面对的是js,有点无从下手的感觉,只能照猫画虎,把open()函数中的url替换成HPU的课表的url:
这个是课表页面的url
function scheduleHtmlProvider(iframeContent = "", frameContent = "", dom = document)
{//函数名不要动
let http = new XMLHttpRequest()
http.open('GET',"http://218.196.248.100/eams/homeExt.action",false)
http.send()
return http.responseText
}
这就是照猫画虎的代码,我们直接在控制台运行:并添加控制台打印语句(这里推荐使用console.info以便后期E2E测试):
控制台输入代码:
1_2:如何判断请求方式
这时也可以看到控制台打印出了我们所要的东西,及该也面的源码。
注意!注意!注意!重要的事情说三遍:我们如何判断网页是否是允许GET方式获取的?那么如果允许它的url到底怎么写?经过我的咨询标准操作应该是这样的:
1.按住ctrl+shift+i或f12调出控制窗口
2.在第一个栏里面找到第四个选项卡Network
3.然后clear历史记录
4.刷新页面返回到登录页面
5.找到第一个出现的请求文件
6.打开起请求头Headers
获取方式:GET
返回码:200(返回200意味请求头请求成功)
例如最常见的的请求码404代表的意思就是浏览器发出请求后,未在服务器找到相应的资源,及404 not found
根据这个我们还可以看出我们所写的url后面应该为loginExt.action,open()函数中的请求方式为GET
至此,我们就把网页的html爬取下来了,为了验证返回结果我们进入parser函数。
2_0:定位课表所在HTML标签
开发者调试窗口然后点开左上角的箭头定位课程表内容
python中对html标签解析是对json格式操作的:
而js则需要用到jQuery选择器,简单来说就是它可以通过id,class等来获取html标签内容,具体内容可以查看上文中提到的文档,这里咱们只需要用到一点
首先介绍几个简单的html标签:<table>表格</table>,<th>表头</th>,<td>表属性,内容</td>
从图中可以看出课表的内容在td子标签中,该子标签被包含在table这个父标签中,所以我们可以写出以下jQuery代码:let $raw = $("#manualArrangeCourseTable .infoTitle");
,前面为id,后面为class。parser函数
function scheduleHtmlParser(html)
{
let $raw = $("#manualArrangeCourseTable .infoTitle");
console.info($raw);
}
运行后返回object数组(并且数组15个元素,正好对应15节课),则provider函数返回参数没得问题,若返回的为空数组则在控制台测试其provider函数返回结果为什么,以及parser函数传入参数的内容.
2_1:思路
这是官方文档中的几个规定变量:
现在我们直接观察数组,对数组进行解析:
从图中可以看出课程名字,老师名字,教室,一共几周都可以看出来,而且都在title里面,我们只需要遍历数组出数组里面的title,然后用字符串切割方法去取对应的内容。这个时候还有一个大问题来了:怎么判断这节课是在星期几,以及有几小节。通过对比每个数组里面的元素不难发现,不同点在id,rowspan里面,其中rowspan代表有几节,id代表的意义我画了张表:
TD这两个字符不变,下划线和下划线后面的字也不变,变的只是TD和_直接的数字,可以这样理解,第一位数字+1表示星期几,第二位则表示第几大节。思路清晰,开干!
2_1:将title,id,rowspan遍历出来
for(str in $raw) //对object数组进行遍历,将id和title提取出来
{
date = $raw[str]
title_text = date.attribs.title;
//里面包含了课程名字,教师名字,周次,教室
//信号与系统(130122020.001) (李辉,张培玲);;;(4-15,南校区 南校区1号教学楼 1207)
id_text = date.attribs.id;
//用来获取星期几低级大节
rowspan_text = date.attribs.rowspan;
//通过来判断有几小节课
/*********控制台输出测试片段********/
//方便测试调试
console.info(title_text);
console.info(id_text);
console.info(rowspan_text);
/*********************************/
}
输出结果:
2_2:如何提取字符串内容
需要用到的几个js函数:
indexOf()//从左开始某个元素第一次出现的位置
lastIndexoOf()//从右边开始某个元素第一次出现的位置
substring(start, end)//截取从开始到结尾位置之间的所有内容,包括start不包括结尾
substring在线练习
当然也可以用正则表达式:
正则表达式测试
正则表达式入门
2_3:课程,教师,教室,周次
通过上面的遍历,我们现在吧获取到的title字符串拿出来:
信号与系统(130122020.001) (李辉,张培玲);;;(4-15,南校区 南校区1号教学楼 1207)
name:
/*******获取课程名字*****/
//a_1计算出第一个左括号出现的位置
a_0 = title_text.indexOf('(');
//直接获取出name的位置
name = title_text.substring(0, a_0);
console.info(name);
teacher:
/******获取教师名字******/
a_1 = title_text.indexOf(')');
//找出第一个)所在的位置,则老师的姓名即为a_1 + 3,因为中间有个空格
b = title_text.indexOf(';');
//同理,找好合适的定位点
teacher = title_text.substring(a_1 + 3, b - 1);
console.info(teacher);
weeks:
/******获取课程周期******/
//前面我们把name,teacher,都提取出了,现在title整个有用的部分就剩后面了,所以我们把后面单独拉出来
c_1 = title_text.lastIndexOf('(');//最后(出现的位置
c_2 = title_text.lastIndexOf(')');//最后)出现的位置
str_1 = title_text.substring(c_1 + 1, c_2);//将最后一个括号的所有内容拉出来
//4-15,南校区 南校区1号教学楼 1207)
c_3 = str_1.indexOf(',');//,出现的位置
weeks = str_1.substring(0, c_3);
console.info(_get_week(weeks));
positions:
/******教室******/
//V4-15,南校区 南校区1号教学楼 1207
d_0 = str_1.indexOf(' ');
d_1 = str_1.lastIndexOf(' ');
position = str_1.substring(c_3 + 1);//substring如果不写end默认全部
console.info(position);
//南校区 南校区1号教学楼 1207
2_4:星期几,第几节
之所以把这个单独拉出来,是因为前面那几个不需要太多代码,所以没必要写函数,这几个则需要单独写函数里面,一方面是为了好看,二也是为了测试的时候好检查
day:
前面我们讲解了id这个参数每个字符代表的意义,我们先把TD和_中间的数字提取出来,并取模,通过switch来输出多种情况
function _get_day(id)//返回星期几,传入的参数为id_text
{
let week_num;
let num;
if(id.substring(3, 4) == '_')
{
num = id.substring(2, 3);
}
else
{
num = id.substring(2, 4);
}
switch(parseInt(num / 10))
{
case 0:
week_num = 1;
break;
case 1:
week_num = 2;
break;
case 2:
week_num = 3;
break;
case 3:
week_num = 4;
break;
case 4:
week_num = 5;
break;
case 5:
week_num = 6;
break;
default:
week_num = 7;
break;
}
return week_num;
}
section:
返回第几节,且为数组
function _get_section(id, n)//传入id_text,rowspan_text代表传入的数据
{
let section = [];
let num;
let i;
//因为周一为TDx_0,其他为TDxx_0,所以先判断为周一还是其他
if(id.substring(3, 4) == '_')//判断是否为周一
{
num = id.substring(2, 3); //如果为周一,则取第二个字符
}
else
{
num = id.substring(3, 4); //否则取第三个字符
}
num = Number(num);
n = Number(n);
i = num;
switch(num)
{
case 0:
for(num; num < i + n; num++)//
{
section.push({"section":num + 1});
}
break;
case 2:
for(num; num < i + n; num++)//
{
section.push({"section":num + 1});
}
break;
case 4:
for(num; num < i + n; num++)//
{
section.push({"section":num + 1});
}
break;
case 6:
for(num; num < i + n; num++)//
{
section.push({"section":num + 1});
}
break;
default:
for(num; num < i + n; num++)//
{
section.push({"section":num + 1});
}
break;
}
return section;
}
weeks:
前面获取到了第几周,但是格式是4-7,这种的,根据官方文档需要改成数组:
其实这个思路应该很早就有:哈哈,就是招募问卷里面的题
这里引用的还是上文中提到的博客中的
//将每节课的周次按照数组的形式返回
function _get_week(data)
{
//weeks data will be inputed as '4,7-18', to handle ,we will split them by ',' and operate seperately.
let result = [];
let raw = data.split(' ');
let i;
for (i in raw)
{
if (raw[i].indexOf('-') == -1)
{
//create array
result.push(parseInt(raw[i]));
}
else {
let begin = raw[i].split('-')[0];
let end = raw[i].split('-')[1];
for (let i = parseInt(begin); i <= parseInt(end); i++)
{
result.push(i);
}
}
}
//sort the array,
return result.sort(function (a, b)
{
return a - b
});
}
最后直接时间直接套用官方的改成HPU作息时间表,这个不用担心夏季和冬季。
最后,测试结果:AII run successfully
3_0:上传,E2E测试
上述完成后就大功告成了,上传后,在手机端打开vsconsole测试(只有MIUI系统的手机才会有开发者选项vConsole)
可悲的是,小爱同学登不了我们学校的教务处,一直点登录没反应,但是其他手机可以登陆进去(玄学)
不过官方给出了说法:第3点,刚好我们是树伟维的系统
所以,最后的提交,内测,过程我就不演示了,大家可以看我开头提到的博客,里面有提到。
最后也非常感谢二氢茉莉酮酸甲酯的指导与帮助,毕竟我也不是计算机专业的,js也是学了没几天,所以欢迎大家来讨论,如果有有问题的小伙伴可以联系:
email:15513924345@163.com
QQ:1639446186