第二次结对作业:班级成绩表
作业要求 | 班级成绩表 |
---|---|
作业目标 | 通过网络爬虫来获得自己想要的信息,而并非读取本地的html文件 |
作业源代码 | 我们的码云仓库 |
林佳森 | 211806327 |
林俊威 | 211806329 |
时间记录
- 代码129行
- 分析时间1.5小时
- 编码时间4小时
本次作业相对轻松,因为在上次个人作业中,我们都已经熟悉了Jsoup的基本功能,当看到需要存储三个变量时,也立即想到了对象数组,就是IO流模块还不是很熟练,参考了我们上学期作业中的例子,也勉强做出来了,其它遇到的一些像获取的活动地址的再处理、学号的获取、存在退出云班课的人、排序时,对象数组不能存在空对象等等难题,也被我们仔细地观察、分析,一一解决了。实现过程相对于上次个人作业来说,相对轻松,或许这就是结对编程的魅力吧。
结对感受
一回生,二回熟,你搬张椅子,我放首歌,结对编程开始了。我们相互讨论,提出彼此的想法,获得更好的方法。我们彼此分工,我来获取内容,并存储数据;你来对数据进行处理,并进行文件输出。我写完,轮到你写,结对编程,真快乐。
搭档评价
- 我对于我搭档的评价:林俊威同学,现实中他唯唯诺诺,键盘上他重拳出击,与我在中国特色社会主义的领导下完成了此次作业,好!
- 我搭档对于我的评价:林佳森同学代码写的又好,人长的又帅,以先富带动后富的精神,指导了我在完成作业过程中很多不太理解的地方,好!
需求分析
1、cookie的导入
- 首先这次与第一次编程作业有些不同,上次作业中是由本地的html文件中获取,而本次中需要从在线的网站中获取,于是便需要导入cookie,好在有老师的发的网站中便能轻松完成。
- 为了避免将“config.properties”乱放的情况,所以我们在这里使用了方法来获取它在项目中的路径来导入url和cookie
String path = Team.class.getResource("config.properties").getPath();
Properties config = new Properties();
config.load(new FileInputStream(path));
Document doc = Jsoup.connect(config.getProperty("url")).header("Cookie", config.getProperty("cookie")).timeout(10000).get();
2、从网页中获取课堂完成部分的div小块来获取每个课堂完成部分的网址并保存
- 通过
getElementsByClass("interaction-row")
获得一个个活动的div小块,再通过contains("课堂完成")
来获得课堂完成部分的所有div小块
而我们需要的网址其实就在其中,也就是data-url
中的内容 - 获取
data-url
中的网址
但其实从中取出来的网址是不能直接用的,眼尖的话,你可以来试试看找出下面这两个网址的区别哦
https://www.mosoteach.cn/web/index.php?c=interaction_homework&m=homework_result_list&clazz_course_id=9E603F91-4AF8-11EA-9C7F-98039B1848C6&id=2C12ADF1-FACD-2647-69C5-961046B2E302&order_item=group
https://www.mosoteach.cn/web/index.php?c=interaction_homework&m=homework_result_list&clazz_course_id=9E603F91-4AF8-11EA-9C7F-98039B1848C6&id=2C12ADF1-FACD-2647-69C5-961046B2E302&order_item=group
当你打开第一个网址时一定是如下图所示,它去了火星
而你打开第二个网址就会是正常的网址
这两个之中最关键的地方就在于“&”,应该把它改成“&”,才能正常打开,这也是我们一番观察之后才发现的,关于这个问题,我们猜想可能是编码问题
所以我们使用了正则表达式"data-url=\"(\\S+)\""
,然后用replace()方法来替换成为正确的网址,并存入之前创建好的足够大的用来存放网址的数组
String url = sort(p, "data-url=\"(\\S+)\"");
webPages[webNum] = url.replace("&", "&");
webNum++;
此处的sort()为我们定义的用正则表达式来获取需要内容的方法,因为后面经常用到,就写成了方法,方便直接调用
3、遍历访问每个网站,并获取所有人的个人信息和经验值,并保存
- 从数组中读取各个网址,再次通过Jsoup来获取我们需要的值
(康康这里!!!)
本来进行到这里很顺利,但当我们测试输出所获取的内容时,嗯?怎么缺斤少两了?云班课上的人怎么就变那么点了?
通过搜索,我们才知道原来是通过Jsoup所获取的内容一但超过1024KB,数据就只有预期得到数据的前1024KB字节了。
所以要在解析的地方加上一句maxBodySize(0)
Document student = Jsoup.connect(webPages[i]).header("Cookie", config.getProperty("cookie")).timeout(10000).maxBodySize(0).get();
- 同样,我们还是可以通过
getElementsByClass()
方法来区分开每一个人
Elements stuExp = student.getElementsByClass("homework-item");
- 获取经验值,我们可以使用正则表达式
int exp = Integer.parseInt(sort(p, "(\\d+) 分</span>"));
- 获取姓名,我们还是可以通过正则表达式
String name = sort(p, "color: #333;\">(\\S+)</span>");
- 获取学号,最麻烦的来了,这也让我们头疼了好一阵子
灾难发生了,前后都有换行符,这种情况,正则表达式,我们不会使用了
而我们也想过使用"211"开头的学号来获取,结果看了眼云班课,心都凉了半截,有乱填学号的!!!
但好在天无绝人之路,我们又发现了新的突破点
再次调用getElementsByClass()
来打破僵局,学号就是最后一个div下的内容,所以
p = p.getElementsByClass("member-message").first();
String id = p.select("div").last().text();
4、创建一个全局对象数组来存储学号、姓名、经验值
- 创建一个对象数组类Student类,设置三个私有变量"姓名、学号、经验值",并自动生成各种方法,并写两种构造方法(无参数、带三个参数),重写equals()、toString()方法,并完成Comparable接口,方便后面排序
- 获取成员数目(该数据在第一次作业中samll.html所在的那个网页),来改变数组的大小,否则后续排序时,会因为后面部分的空对象数组,而无法进行
同样,这个成员数目,也不是特别好处理
我们直接通过select方法来获取了“(73)”,就只要去掉这个括号就可以了
正巧我们最近在学习的PHP中有一个方法substr()
方法,通过联想,我们竟然真的在API中找到了类似方法substring()
所以如下处理后,成员的数目就得到了
String memNum = doc.getElementById("menu-content-box").select("a").get(1).select("span").get(1).text();
stuNum = Integer.parseInt(memNum.substring(1, memNum.length() - 1));
stuList =new Student[stuNum];
- 剩下的就是简单的存入数据了,但其中还是存在着一些问题,会有人退出云班课!!!而且每次作业中都会有他们原先的作业记录
这也就意味着你获取的成员数会比能爬取获得出来的总人数来得少,会导致数组溢出。
但写了那么久,我们实在不想换成集合来实现了。真要实现的话,我想那应该是我们中了彩票后,数钱数到无聊的时候,就会去做吧
所以我们通过观察发现,在我们获得的数据中,退出的人是没有姓名的,因为不符合我们的正则表达式"color: #333;\">(\\S+)</span>"
所以只需要在存储过程中加入一个if(!name.equals(""))
判断,就可以过滤掉他们了。
5、处理出需要的数据,并输出保存成txt文件
- 排序,只需要重写Student类中的
compareTo()
方法,再调用Arrays.sort()
方法就可以完成了
public int compareTo(Student o) {
int sort1 = o.exp-this.exp;
int sort2 = sort1 == 0 ? this.id-o.id : sort1;
return sort2;
}
- 打印输出,通过PrintWriter类,写入,最后关闭,就可以在项目文件夹中生成一个“score.txt”文件了
File txt = new File("score.txt");
PrintWriter out = new PrintWriter(new FileWriter(txt));
String first = "最高经验值为:"+stuList[0].getExp()+","+"最低经验值为:"+stuList[stuNum-1].getExp()+","+"平均经验值为:"+aver/stuNum;
out.print(first+"\n");
for(Student stu:stuList)
out.print(stu.toString()+"\n");
out.close();
我们班级中真的有经验值为0的人(下图为云班课的总经验值,非单独课堂完成部分的经验值)
优化过程
- 在获取分中,如果按照我们之前的正则表达式,会获得助教评的分数,而非最终分数,正常时候,这两个是会相同的,但有时候是会存在不同的
而优化也很简单只需要将我原本的正则表达式中的if
改为while
,这样,就能匹配到最终分数
作业小结
此次作业让我们收获了很多,不仅学会了如何从在线网站中获取我们需要的信息,还让我们好好地复习、巩固了以前的一些知识,也让我们看到
了自身的弊端——对于IO流的应用掌握情况几乎为0,我们也决定对这一方面的相关知识好好恶补一番,书到用时方恨少啊。
参考文献
导入cookie:https://www.cnblogs.com/jamaler/p/11645569.html
Jsoup获取响应内容不完整:https://blog.csdn.net/qq_40180411/article/details/103746271
JsoupAPI:https://jsoup.org/apidocs/org/jsoup/nodes/Element.html
jdk api 1.8(我们参考下载在本地的API,没法提供网址)