zoukankan      html  css  js  c++  java
  • 结对第二次—文献摘要热词统计及进阶需求

    课程 软件工程1916|W(福州大学)
    作业要求 结对第二次—文献摘要热词统计及进阶需求
    结对博客 221600426     221600401
    Github基础需求项目地址 221600426     221600401
    Github进阶需求项目地址 221600426     221600401
    作业目标 实现一个能够对文本文件中的单词的词频进行统计的控制台程序,并能在基本需求实现的基础上,通过爬取CVPR2018官网并进行顶会热词统计
    具体分工 221600426负责主要代码的编写,221600401负责学习爬虫和博客的撰写

    Github的代码签入记录:


    PSP表格

    PSP2.1 Personal Software Process Stages
    Planning 计划
    Estimate 估计这个任务需要多少时间
    Development 开发
    Analysis 需求分析 (包括学习新技术)
    Design Spec 生成设计文档
    Design Review 设计复审
    Coding Standard 代码规范 (为目前的开发制定合适的规范)
    Design 具体设计
    Coding 具体编码
    Code Review 代码复审
    Test 测试(自我测试,修改代码,提交修改)
    Reporting 报告
    Test Repor 测试报告
    Size Measurement 计算工作量
    Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划
      合计

    解题思路描述:即刚开始拿到题目后,如何思考,如何找资料的过程

        首先是语言的选择,由于221600426较常做LeetCode题,以及较常使用.net,所以开头是打算使用C++来开发的,后来较详细的分析了需求后发现对于此题,我们可能会需要用到大量的正则表达式以及爬虫,java有大量的类库以及前人的经验,所以最终选择用java来实现。然后是爬虫框架的选择,goole了一下,搜到较多的jsoup实现爬虫的相关信息,因此我们就选择了学习jsoup;分析程序要实现的基本功能,后确认至少需要有三个接口(字符计数,行计数,单词计数),将其封装成一个类不妨称为WordsHandler,最后针对每一个功能进行编码的实现,以及进行模块测试,确保独立模块稳定,再进行拼接以及整体测试。基本功能实现后就可以考虑进阶需求,先在jsoup Cookbook(中文版)学习jsoup,然后边学边写爬虫,最后在基础功能之上通过小修改即可实现进阶需求。

    基础需求设计实现过程:

    • 代码如何组织
      共需两个类,一个程序入口Main,一个文本处理WordsHandler。Main实例化一个WordsHandler,该实例在调用其接口charCnt,lineCnt,wordCnt进行字符,有效行数,单词数统计,最后调用printInfo进行输出。

    • 单元测试的设计

    • 代码组织与内部实现设计(类图)

    • 算法的关键与关键实现部分流程图
      基础需求较为简单,硬要找到算稍微难点的,应该是wordCnt的实现。该接口接收一行字符串,首先用正则表达式"[^a-zA-Z0-9]"匹配非字母数字符号的位置,分割出所有的可能单词(存在不符合题目定义"必须4个字母开头"),然后把所有单词转为小写,并遍历单词数组,如果该词匹配正则"[a-z]{4}[a-zA-Z0-9]*"则它就是题目定义的单词,那么就把单词总数+1,并判断单词map中是否存在以该单词为key的二元组,若不存在则将单词作为key,并用1作为value存入map,否则在对应key的位置将其value+1。

        //单词数统计
    	public void wordCnt(String line) {
    		String arr[]=line.split("[^a-zA-Z0-9]"); //分割出潜在的单词
    		tolowerCase(arr);
    		for(int i=0;i<arr.length;++i) {
    			//System.out.println(arr[i]);
    			if(arr[i].matches("[a-z]{4}[a-z0-9]*")) {//判断是否是单词
    				wordCnt++;
    				if(!wordCntMap.containsKey(arr[i])) {
    					wordCntMap.put(arr[i], 1);
    				}else {
    					wordCntMap.put(arr[i], wordCntMap.get(arr[i])+1);
    				}
    				
    			}
    		}
    	}
    

    进阶需求设计实现过程:

    • 代码如何组织
      wordCount进阶设计了三个类,分别是Main,WordsHandler,Option。Main是程序的入口,实例化一个Option对象,调用该对象的getOption接口获取操作参数;然后实例化一个WordsHandler对象,调用该对象的readFile读取文件,并在每读取一行后调用charCnt,lineCnt,wordCnt进行字符,有效行数,单词总数统计,若m>1则调用worsCnt进行词组统计。最后调用writeFile进行输出。爬虫采用两个类,分别是Main,Handler。Main实例化一个Handler,在获取到目标所有链接后,筛选出具有”content_cvpr_2018/html/“头部的链接,并使用线程池调用Handle对象的同步方法writeFile进行论文爬取并输出。

    • 单元测试的设计

    • 代码组织与内部实现设计(类图)

    • 算法的关键与关键实现部分流程图
      进阶需求较难的地方是单词词组的统计,以及爬虫的效率和同步代码块的设计。(由于Option.isWeight仅仅是权值的不同,以下仅解释当启用权值的情况)wordsCnt接收一行字符串,调用getWords分割出所有的可能单词(存在不符合题目定义"必须4个字母开头",正则表达式为"[^a-zA-Z0-9]"),调用getSeperator分割出所有的分隔符(正则表达式为“[a-zA-Z0-9]+”);然后遍历单词数组i=0->letters.length-m查找连续的m个单词,首先置find为true,然后从i位置连续遍历m此单词数组,若存在一个位置不符合题目单词的定义则置find=false,二层循环结束后判断find是否为true,若为true则表示存在连续的m个单词,那么从位置i开始拼接单词和其原本的分隔符,并根据该串是在title行还是在abstract行分别进行map的存取。

       //单词组计数
    	public void wordsCnt(String line) {
    		if(Option.isWeight) {//启用权值
    			String letters[]=getWords(line); //分割出潜在的单词
    			String separators[]=getSeperator(line);//分割出分隔符
    			for(int i=0;i<letters.length-Option.m+1;i++) {
    				boolean find=true;
    				for(int j=i,cnt=0;cnt<Option.m;++j,cnt++) {
    					if(!letters[j].matches("[a-z]{4}[a-z0-9]*")) {//判断是否是单词
    						find=false;
    					}
    				}
    				if(find) {//找到连续的m个单词
    					String str=letters[i];
    					for(int j=i+1,cnt=0;cnt<Option.m-1;++j,cnt++) {
    						str+=separators[j-1]+letters[j];
    					}
    					if(line.contains("Title: ")) {
    						if(!wordCntMap.containsKey(str)) {
    							wordCntMap.put(str, 10);
    						}else {
    							wordCntMap.put(str, wordCntMap.get(str)+10);
    						}
    					}
    					if(line.contains("Abstract: ")) {
    						if(!wordCntMap.containsKey(str)) {
    							wordCntMap.put(str, 1);
    						}else {
    							wordCntMap.put(str, wordCntMap.get(str)+1);
    						}
    					}	
    					
    				}
    			}	
    		}
    		else {//不启用权值
    			String letters[]=getWords(line); //分割出潜在的单词
    			String separators[]=getSeperator(line);//分割出分隔符
    			for(int i=0;i<letters.length-Option.m+1;i++) {
    				boolean find=true;
    				for(int j=i,cnt=0;cnt<Option.m;++j,cnt++) {
    					if(!letters[j].matches("[a-z]{4}[a-z0-9]*")) {//判断是否是单词
    						find=false;
    					}
    				}
    				if(find) {//找到连续的m个单词
    					String str=letters[i];
    					for(int j=i+1,cnt=0;cnt<Option.m-1;++j,cnt++)
    						str+=separators[j-1]+letters[j];
    					if(!wordCntMap.containsKey(str)) {
    						wordCntMap.put(str, 1);
    					}else {
    						wordCntMap.put(str, wordCntMap.get(str)+1);
    					}
    				}
    			}
    		}	
    	}
    

    爬虫部分的实现采用jsoup,首先爬取http://openaccess.thecvf.com/CVPR2018.py官网的所有a标签,然后遍历所有a标签数组,若该a标签的href包含“content_cvpr_2018/html/”则它就是具有论文html的页面,将其完全限定路径取出并使用线程池开启一个线程去爬取论文。线程会调用Handler的同步方法writeFile(该方法共享cnt变量(论文数量),以及文件指针)对目标论文进行按要求爬取。

    //Main函数部分代码
    try {
    			System.out.println("开始链接");
    			Document document=Jsoup.connect("http://openaccess.thecvf.com/CVPR2018.py").maxBodySize(0).timeout(1000*60).get();
    			System.out.println("开始爬取");
    			handler.writer=new BufferedWriter(new FileWriter("result.txt"));
    			//System.out.println(document.toString());
    			Elements links=document.getElementsByTag("a");
    			int cnt=0;
    			for(Element link:links) {
    				String href=link.attr("href");
    				//System.out.println(href);
    				if(href.contains("content_cvpr_2018/html/")) {//获取论文
    					cnt++;
    					cachedPool.execute(new Runnable() {
    						@Override
    						public void run() {
    							// TODO Auto-generated method stub
    							try {
    								//Thread.sleep(100);
    								Document document=Jsoup.connect("http://openaccess.thecvf.com/"+href).maxBodySize(0).timeout(1000*60*5).get();
    								handler.writeFile(document);
    							} catch (Exception e) {
    								// TODO Auto-generated catch block
    								e.printStackTrace();
    							}
    						}	
    					});
    					
    				}
    			}
    			System.out.println(cnt);
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    
    class Handler{
    	BufferedWriter writer;
    	int cnt=0;
    	synchronized void writeFile(Document document) {//目标内容爬取
    		try {
    			System.out.println(cnt+" "+document.getElementById("abstract").text());
    			String string=cnt+"
    ";
    			string+="Title: "+document.getElementById("papertitle").text()+"
    ";
    			string+="Abstract: "+document.getElementById("abstract").text()+"
    
    
    ";
    			writer.write(string);
    			writer.flush();
    			cnt++;
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    	
    	@Override
    	protected void finalize() throws Throwable {
    		// TODO Auto-generated method stub
    		super.finalize();
    		System.out.println("close");
    		writer.close();
    	}
    }
    

    改进的思路:

    • 基础需求的改进:原本采用三模块子功能划分导致过度的IO,会使性能下降;改进措施是先对文件扫描一遍导入内存,三个子功能分别对内存中的文件镜像进行操作,这样减少了IO,在文件过大的情况下,可以有效提高性能。
    • 进阶需求的改进:由于进阶是在基础之上,基础的改进同样适用,主要不同在于进阶需要进行词组统计,这里我们采用了2重循环,最坏情况下时间复杂度是O(mn),由于m<=100,所以可以认为最坏的时间复杂度是O(n),该出尚未想到更加的优化策略。爬虫方面的优化在于原本采用单线程去爬取(有978条数据)使用的时间超过5分钟,所以我们就采用了多线程技术,多线程碰到该怎么设计同步方法,该方法必须尽可能的短才能有效的优化效率,最后抽取出共享cnt变量(论文计数)和文件指针,方法体仅进行目标爬取。

    项目测试:

    • 基础需求的字符,单词,行数少量计数(有10个测试样例,这里只贴出2个)


    • 基础需求的压力测试

    • 进阶需求的官网论文测试

    遇到的困难和解决方法:

    • 1.需求不明确,Edge Case模糊
      解决方法 :在群里与同学,助教交流

    • 2.初次使用java爬虫
      解决方法 :上网找教程自学爬虫

    • 3.结对成员上课时间冲突,未能深入讨论代码实现
      解决方法 :在双方都没课时,约个时间讨论实现方案;用qq保持交流,实时分享编程进度

    • 4.对于“统计文件的字符数”的功能中一些字符该如何统计未能很好的理解
      解决方法:在微信群和博客中向助教和老师提问直到彻底理解需求,对程序根据助教提供的样例进行测试

    评价队友

      221600426
    队友积极配合,细心,耐心,具有上进心,两次作业合作下来非常愉快。
      221600401
    我的队友代码能力超强!之前只是听说我的队友队友是个大佬,结对后才深刻体会到他编程的能力,记得今天才刚分好工,第二天就完成了基础的编程,作为一队有一种强烈的不能偷懒的想法。我的队友学习能力也超强,上次作业的墨刀和这次作业的爬虫,感觉很快就能学会并且自己开始做了,对我提出的问题也会认真讲解。我希望能学到队友那超强的编程能力和学习能力,在作业过程中能不拖累队友!
  • 相关阅读:
    第二周作业
    20172331 《Java程序设计》第1周学习总结
    20172316《程序设计与数据结构》(下)课程总结
    哈夫曼编码测试博客
    20172316 2017-2018-2 《程序设计与数据结构》实验三报告
    20172316 2018-2019-1《程序设计与数据结构》第九周学习总结
    20172316 2018-2019-1 《程序设计与数据结构》实验二报告
    2018-2019-1 20172316《程序设计与数据结构》第八周学习总结
    20172316 2018-2019-1《程序设计与数据结构》第七周学习总结
    20172316 《程序设计与数据结构》第六周学习总结
  • 原文地址:https://www.cnblogs.com/orangepoem/p/10537815.html
Copyright © 2011-2022 走看看