第二次结对作业(陆桂莺+崔亚明)

作业要求 这份作业要求的链接
作业目标 <根据要求编写代码,建仓,用博客记录作业过程>
作业源代码 码云仓库地址
崔亚明 <211806313>
陆桂莺 <211806398>

编码记录

代码行数 230行
计划 30min
需求分析时间 30min
一起编码 12h
一起复审代码 5h
学习时间 3h

开始作业前我们的想法和我们认为存在的难点

  我们对爬虫这个领域一无所知,所以在没有开始写作业之前我们觉得有点害怕,觉得这次作业太难,如何网络爬取我们需要的数据,如何得到课堂完成部分的所有url再进行解析,如何得到每个同学的信息,如何处理得到的数据,都是让人头大的问题。

需求分析

  网络爬取云班课的数据,将云班课上全班的课堂完成部分的经验值爬取下来,根据经验值排序,看看自己和自己的同学在全班第几名,同时计算出平均经验、最低经验、最高经验。

我们的代码思路步骤

  分解为五个模块(按功能划分,比较直观):解析源文件——》获取课堂完成部分的所有链接——》获取学生信息并整合——》写入数据——》main函数里按步骤运行

(模块一)读取源文件的url和cookie信息

  • 设置URL和COOKIE为全局变量
  • 用一个方法来读取配置文件里的url和cookie信息,赋值给URL和COOKIE

(模块二)获取课堂完成部分活动的所有链接

(1)查找规律

  • 所有活动都被class="interaction-row"的div包裹着
  • 这个div里的属性data-url存放着相应活动页面的url

(2)获取课堂完成部分的所有链接

  • 手动设置cookies,解析html
Document document = Jsoup.connect(URL).header("cookie", COOKIE).get();
  • 存储课堂完成部分的所有链接
//获取到所有活动的div
Elements activDivs = document.getElementsByClass("interaction-row");
//初始化存储课堂完成部分链接的列表
ArrayList<String> baseList = new ArrayList<String>();
//获取课堂完成部分活动的url
for(int i = 0;i < activDivs.size();i++) {
	if(activDivs.get(i).child(1).child(0).child(1).text().indexOf("课堂完成") != -1) {
	      //将属性data-url的值转为字符串
	      String urlString = activDivs.get(i).attr("data-url").toString();
	      //把值加到baseList
	      baseList.add(urlString);
	}
}

(模块三)整合学生信息列表

(1)解析所有课堂完成部分的url

//初始化一个列表存储解析课堂完成部分的html文件的document对象
ArrayList<Document> baseActives = new ArrayList<Document>(baseList.size());
//存储解析课堂完成部分的html文件的document对象
for(int i=0;i<baseList.size();i++) {
	//解析课堂完成部分
	Document document = Jsoup.connect(baseList.get(i)).header("cookie", COOKIE).get();
	//将解析后的document添加到baseActives
	baseActives.add(document);
}

(2)查找所需数据存在的规律进行操作

  • 每一个学生的作业情况都被class="homework-item"的div包裹着
  • 创建学生对象保存学生信息(包含学号,姓名和成绩,包含get,set方法,重写toString()和compare()方法)
  • 学号,姓名和经验值也在特定位置的节点文本里,可以按规律获取到
//学生
Student stu = new Student();
//设置学号
stu.setStuNo(baseStuDivs.get(k).child(0).child(1).child(1).text().toString());
//设置姓名
stu.setStuName(baseStuDivs.get(k).child(0).child(1).child(0).text().toString());
//获得成绩文本
String score = baseStuDivs.get(k).child(3).child(1).child(1).text();
  • 要考虑到没评分和未提交的情况,没评分和未提交成绩都为0
  • 去重并计算总经验值,把新的对象加入新的列表
for(int i=0;i<stuList.size();i++) {
        //初始化每个学生的总成绩
        scoreDouble = 0.0;
	//学生
	Student student = new Student();
	for(int j=i+1;j<stuList.size();j++) {
		if(stuList.get(i).getStuName().contains(stuList.get(j).getStuName())) {
		      //计算成绩
		      scoreDouble += stuList.get(j).getStuScore();
		      //删掉重复的
		      stuList.remove(j);
	        }
        }
        //学生和学生的总成绩的学生对象
        student.setStuNo(stuList.get(i).getStuNo());
        student.setStuName(stuList.get(i).getStuName());
        student.setStuScore(scoreDouble);
        //加入到新列表
        studentList.add(student);
}

(模块四)将数据写入score.txt(此部分内容在主函数对学生列表排序后进行)

  • 在进行这一步骤时复习回顾了上学期学的IO流与Stream流才完成的代码
  • 最高经验值是列表里第一个学生的经验值,最低经验值是列表里最后一个学生的经验值
  • 平均经验值通过把所有同学的经验值相加后除以列表成员的数量得到
//确定输出文件的目的地
File file = new File("score.txt");
//创建指向文件的打印输出流
//追加写
PrintWriter printWriter = new PrintWriter(new FileOutputStream(file),true);
//输出数据
double ave = 0.0;
//计算总分
for (int j = 0; j < students.size(); j++) {
	ave += students.get(j).getStuScore();
}
//平均分
ave = ave/students.size();
//写入信息
printWriter.println("最高经验值"+students.get(0).getStuScore()+",最低经验值"+students.get(students.size()-1).getStuScore()+",平均经验值"+ave);
//写入学生信息
for (int i = 0; i < students.size(); i++) {
	printWriter.println(students.get(i).toString());
}
//关闭
printWriter.close();

(模块五)main函数运行顺序

  • 复习回顾了上学期学的集合和泛型进阶里的比较器排序才得以完成这部分
//读取源文件的url和cookie
getResources();
//获取所有课堂完成部分的url
ArrayList<String> baseList = baseList();
//学生信息
List<Student> students = stuList(baseList);
//排序(成绩降序,学号升序)
Collections.sort(students,new Student());
//写入
write(students);

运行结果(以下显示为记事本打开后显示的内容)

我们的代码存在的问题

  • 运行结果不太正确,应该是获取数据去重这里出了问题,导致最后计算结果不正确还出现了重复的学生信息,但不知如何修改……
  • 在运行编码获取不同活动学生信息和成绩时报java.lang.IndexOutOfBoundsException,不知如何拯救就直接tryCatch,估计这里弄丢了部分学生信息,导致最后最高分低于122
  • 感觉代码的运行时间不够快,主要是因为循环的次数太多,但不知应该如何简化来提高代码的效率

改进

——————感谢助教的帮助让我们顺利完成代码

  • 让jsoup能够读取到全班同学的信息,修改了解析课堂完成部分的代码
Document document = Jsoup.connect(baseList.get(i)).maxBodySize(0).header("cookie", COOKIE).get();
  • 运行效率改进
    • 扔掉了原有的双层循环去重,在第一个课堂完成部分获取所有学生的学号姓名,初始化成绩为0.0
    • 第一个课堂完成部分是老师布置的最后一个课堂完成部分,已经没有了退出班课的同学
    • 把学号作为key,学生对象作为value,把学生和学号之间关联起来,方便之后学生的成绩计算
    • 避免排序错误,这里把学号为空的学号设为0000
 		//学生map集合
		Map<String, Student> stuMap = new HashMap<String, Student>();
		//获取第0课堂完成部分的作业提交信息的elements
		Elements baseActive0 = baseActives.get(0).getElementsByClass("homework-item");
		for(int q=0;q<baseActive0.size();q++) {
			//学生
			Student stu = new Student();
			//设置学号
			stu.setStuNo(baseActive0.get(q).child(0).child(1).child(1).text().toString());
			//设置姓名
			stu.setStuName(baseActive0.get(q).child(0).child(1).child(0).text().toString());
			//初始化成绩
			stu.setStuScore(0.0);
			//避免排序错误,这里把学号为空的学号设为0000
			if (stu.getStuNo().equals("")) {
				stu.setStuNo("0000");
			}
			//把当前学生加入学生map集合,学号为key,学生对象为value
			stuMap.put(stu.getStuNo(), stu);
		}
  • 计算过程修改
    • 发现没有提交就不会有class="appraised-box cl",就以此来判断是否提交
    • 发现还存在虽然尚未评分但有成绩的情况,增添了这部分的计算
    • 如果学号匹配上了stuMap里的键,就把学生成绩累加起来
                Elements baseStuDivs;//每个活动的学生作业情况
		String scoreString;//成绩文本
		Double scoreDouble;//成绩
		String nowStuNo;//正遍历的学生学号
		for(int i=0;i<baseActives.size();i++) {
			//获取每个课堂完成部分网页里的class="homework-item"的div
			baseStuDivs = baseActives.get(i).getElementsByClass("homework-item");
			for (int j = 0; j < baseStuDivs.size(); j++) {
				//如果有提交就会有class="appraised-box cl"
				if (baseStuDivs.get(j).child(3).className().contains("appraised-box cl")) {
					//最终得分为尚无评分时直接遍历下一个学生
					if (baseStuDivs.get(j).child(3).child(1).child(1).text().contains("尚无评分")) {
						continue;
					}
					//成绩文本
					scoreString = baseStuDivs.get(j).child(3).child(1).child(1).text();
					//分离出经验值
					scoreDouble = Double.parseDouble(scoreString.substring(0, scoreString.length() - 2));
					//正在遍历的学生学号
					nowStuNo = baseStuDivs.get(j).child(0).child(1).child(1).text();
					//为了匹配之前学号为空的人
					if(nowStuNo.equals("")) {
						nowStuNo = "0000";
					}
					//如果学号匹配上了stuMap里的键,就把学生成绩累加起来
					if (stuMap.containsKey(nowStuNo)){
						stuMap.get(nowStuNo).setStuScore(stuMap.get(nowStuNo).getStuScore() + scoreDouble);
					}
				}
			}
		}
  • 去掉了学生类的public
  • 改进后结果变正确啦

我们的commit信息

结对照片


结对作业感受

  相较于第一次合作,第二次合作也就更加的得心应手,都了解了对方的思维模式,每人有不同的分工,遇到难题共同克服,共同学习,共同进步。我们虽不是最无敌的组合,但我们一定是最契合的队友。

  通过完成这次的作业,发现编程是需要勤练习勤实践的,好多上个学期学过的内容,到现在已经忘得差不多了,看到之前学的内容就好像是新的知识点一样。以后学习不能仅仅只停留于纸上,还要多多复盘,多打代码。

  每次作业都是一种自主的学习,通过这次作业我们学会了如何在网站中获取所需要的信息,也复习了之前的上课内容,不断调整代码,我们也还是出现了一些问题,知道问题出在哪里但不知道应该怎么改,手足无措,希望助导和老师能给些建议。

相互评价

  • 陆桂莺:在以前我比较喜欢自己一个人完成一件事情或者是作业,得益于这两次的结对作业,让我感受到两个人一起分担作业的快乐,感受到思想碰撞的火花。亚明有自己的想法,这些想法让我眼前一亮;亚明也很有耐心,在我暴躁的时候能让我冷静下来想问题。

  • 崔亚明:作为一个同为集美貌和智慧于一身的小仙女们,我们惺惺相惜,桂莺对代码及有耐心,还能极快速的解析题目要求,这也是合作中不可或缺的能力,作为她的队友我感到很荣幸。

参考内容

(1) chrome浏览器查看当前页面cookie

(2) Java 爬虫遇到需要登录的网站,该怎么办?

(3) 上学期小班课里的:11-IO流与Stream流,06-Map集合和泛型进阶
(4) 参考文档:JDK_API_1_6_zh_CN.chm

posted @ 2020-10-02 19:52  呜啦啦的神  阅读(142)  评论(0编辑  收藏  举报