福大软工1816 · 第五次作业 - 结对作业2
分工以及代码规范
- 分工
- 张扬大佬负责WordCount、测试部分、附加题
- 李泓负责了网络爬虫的实现
- 代码规范
- C++代码规范符合Google C++编程规范
- Python代码符合Pep 8代码规范
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
Planning | 计划 | 30 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | 690 | 960 |
· Analysis | · 需求分析 (包括学习新技术) | 30 | 240 |
· Design Spec | · 生成设计文档 | 30 | 30 |
· Design Review | · 设计复审 | 20 | 20 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 20 |
· Design | · 具体设计 | 30 | 30 |
· Coding | · 具体编码 | 360 | 450 |
· Code Review | · 代码复审 | 30 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 180 | 140 |
Reporting | 报告 | 110 | 80 |
· Test Repor | · 测试报告 | 30 | 20 |
· Size Measurement | · 计算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 40 |
合计 | 830 | 1070 |
解题思路描述与设计实现说明
爬虫的使用
查看了C++的爬虫,差不多是基于WinSocket
拿到HTML信息,爬下来再去分析即可,工作量可能不小。
负责爬虫的队友国庆期间事情有点多,最后没有使用C++来实现爬虫
- 爬虫环境:Python3.6 Windows 10
- 爬虫代码链接
- 使用的库包括
requests
、beautifulsoup
,其中requests
库用来爬取HTML信息,beautifulsoup
库用来美化HTML信息并从HTML中查找标签信息 - 针对CVPR官网网络IO较慢,容易造成爬取中断的问题,使用及时存储方法, 大大增加了爬虫的健壮性
- 考虑到CVPR多个论文中存在非ASCII码(主要是一些拉丁文)的问题,使用UTF-8编码保存相关信息,解决了部分拉丁文乱码问题
代码组织与内部实现设计(类图)
应作业要求,本次代码组织如下
原谅本人并不认为文件越多就越好,我们的文件虽然少但结构还算清晰(#.#)
031602345&031602321
|- src
|- WordCount.sln
|- WordCount
|- stdafx.h 预编译头文件
|- WordCount.h 定义外部接口
|- WordCount.cpp main函数入口
|- function.cpp 程序内函数的实现
|- stdafx.cpp 预编译文件
|- WordCount.vcxproj
|- cvpr
|- Crawler.py
|- result.txt
程序调用结构如图
- 对程序执行过程的解释:
- 主程序首先执行参数解析函数
- 执行
GetWordCountMap()
函数,获得词频统计字典。为了减少不必要的文件IO,故在在获取词频统计字典的过程中顺路得到到characters、words、lines信息,并修改g_has_got_map
、g_has_got_lines
、g_has_got_words
、g_has_got_characters
四个标志位为True - 由于在获取词频字典的时候已经修改了标志位,程序接下来可以直接获取到words、characters、lines信息,而不需要再读取文件
- 执行
GetFirstNWords()
函数,获取rank前N的单词 - 显示结果(输出到文件中)
说明算法的关键与关键实现部分流程图
这次的重点应该是带有指定长度的词组词频统计功能,剩下的功能只要在上一份代码的接口上做一些改动即可
以下是用于实现词组词频的实现过程
附加题设计与展示
附加题1:基于英文分词技术的热词分析功能
-
独到之处:使用英文分词技术,而不是傻大黑粗的指定长度的分词,尽量保留大家感兴趣的专有名词,方便大家看到年度热词(多为专有名词)
-
实现思路:使用python的
textblob
库,对每篇论文分析得到英文词组,同时构建一套简单的规则去除一些常见词,进而得到词频统计。数据展示部分使用了seaborn
库的功能 -
实现成果展示:
附加题2:年度最佳作者排行(基于发布的论文数)
-
独到之处:你想知道CVPR最大牛的是谁吗?我们可以根据每个作者发表的论文数量,简单分析年度最佳作者。注:能力有限,没有考虑重名的问题(实际上这个问题如果要考虑进去,会变得很复杂)
-
实现思路:在爬虫中只要把每篇文章的作者跟着爬取下来,根据作者名出现的次数进行排序即可。
-
实现成果展示:
附加题3:论文翻译工具(还是个半成品)
-
独到之处:可以对爬取到的论文进行翻译, 暂时还是个半成品o(╥﹏╥)o
-
实现思路:这里使用了一个境外服务器来实现翻译
-
实现成果展示:
-
关键代码解释
头文件中定义的外部接口一览
其中Init()
和SetParam()
函数是为了能在测试程序中模拟命令行参数而设计的,在程序运行过程中没用
词组词频统计部分(代码注释中有详细说明每一段代码的功能,其中部分代码被折叠)
性能分析与改进
原始的性能分析如图,其中GetFirstWords
、GetWordCountMap
函数的开销较大
在查看WordCountMap
函数的过程中,发现我本来使用的双端队列的push_back
很慢啊,想了想当初干嘛要用双端队列呢,好像没啥用(可能是方便吧),想先改用队列来看一下
CPU的总计算量从18588下降到了16728(速度提升10%)
后来又想了想,感觉为啥要用队列呢?直接用两个指针不好吗?然后就开始更改成只用两个指针。不过改的过程中出了BUG,还在修复...等修复完了再更新博客
程序中消耗最大的函数是GetWordCountMap
这个家伙
而GetWordCountMap
中占比最大的函数是AddWordWeight
这个函数
在AddWordWeight
这个函数中,我目前使用的是STL::Map,在考虑自己手写一个字符串哈希来代替Map的功能,这部分在我改进完后再更新博客
单元测试
上次因为自己找了很久没找到我VS中的覆盖率分析工具,也怪自己没看到需要至少10组测试样例,吃了没做好单元测试的大亏。这次重新学习VS中单元测试部分,设计了10组测试。
另外字符统计不应该统计/r,在这个版本中做了修正
测试模块名 | 测试描述 | 预期结果 |
---|---|---|
EmptyFileTest | 打开一个空文件 | 字符数、有效行数、单词数均为0 |
CountCharTest | 字符统计测试 | 字符数中'Title:' 'Abstract: ' 论文编号以及多余换行符不应该被统计 |
CharTestForCr | 字符测试 | 字符不应该被统计 |
CountLineTest | 统计有效行 | 只统计Title、Abstract打头的有效行 |
WordInOtherLineTest | 单词统计 用于测试其他行的单词是否被计数 | 只统计Tiltle、Abstract打头的行内的单词,出现在其它行的单词不予考虑 |
WordGroupTest | 词组判断测试,参数m设置为3来测一个含有4个单词的词组 | 理论词组的数量为2 |
MultiListWordGroupTest | 用于测试程序能否保存住中间的分隔符 | 需要保存住中间的分隔符 |
WordGroupValidTest | 词组合法性检查 用于测试中间存在不合法单词时的处理情况 | 当中间存在不合法单词时不应该组成词组 |
WeightTest0 | 用于测试当m设置为1时title的权重是否改变 | Title中的词组的权重应该为10 |
WeightTest1 | 用于测试当m设置为0时title的权重是否为1 | Title中的词组的权重应该为1 |
测试代码例
// 包含多个分隔符的词组检查
namespace MultiListWordGroupTest
{
TEST_CLASS(UnitTest1)
{
public:
TEST_METHOD(TestMethod1)
{
Init();
string input_path = "multilistwordgrouptest.txt";
// 设置词组长度为3
vector<map<string, int>::iterator> first_n_words = GetFirstNWords(input_path, 3, 10000);
/*
multilistwordgrouptest.txt文件中的内容为
Title: Word1 ! word2 + word3
*/
int number_word_groups = first_n_words.size();
// 理论上有1个词组
Assert::IsTrue(number_word_groups == 1);
string word = first_n_words[0]->first;
int equal = word.compare("word1 ! word2 + word3");
Assert::IsTrue(equal == 0);
}
};
}
代码覆盖率测试
主函数所在的cpp文件的代码覆盖率为100%
用于实现各个函数的具体功能的function.cpp代码覆盖率有点低,仅为71%,虽然不高但完完全全符合我的预期。覆盖率低主要是由于项目机构导致的,在获取完词频统计字典后,用于获取字符、获取有效行、获取单词总数的函数就不必调用了。可以发现在function.cpp中未被调用的大部分还是这部分函数
另外由于init和SetParam函数主要是为了测试方便提供出来的接口,在程序运行过程中也没有被调用,和预期结果差不多(要是被调用了那就有鬼了o(╥﹏╥)o)
Github的代码签入记录
未能做到代码一有变更就立马上传,因为有时候改BUG的时候改入迷就忘了,所以有的迁入是多个功能一起迁入的,下次一定想办法改进!下次考虑给自己列一个列表,今天要改哪些功能,每改进一个功能就在git上迁入一次。
遇到的代码模块异常或结对困难及解决方法
遇到的困难 | 做过哪些尝试 | 是否解决 | 有何收获 |
---|---|---|---|
用C++写爬虫 | 查找有没有前辈写过可供参考的文档,看到了winsocket编程,迫于队友较忙个人时间有限,没有使用C++来编写爬虫 | 未解决 | 虽然没啥收获但是有感想 |
重新学习Python爬虫 | 之前还是写过requests、bs4库的,重新学习了一下 | 解决 | 还是python做爬虫容易 |
爬取过程中由于网络问题爬虫容易失败,失败了又要重来,重来的时候又可能被断掉... | 对爬取到的文章做及时存储,已经在本地存在的文章下次就不爬了 | 解决 | 增加了点代码量 |
附加题中的分词 | 尝试过nltk库、textblob库,最终使用了textblob库 | 解决 | 学到了新库的使用 |
附加题分词中,在已经设置仅获取名词的情况下仍然出现了大量如different、learning这样的常用词 | 观察了词的结构,发现大部分的专有名词还是多个单词的复合,于是自己设置了一套规则,用于屏蔽differet、learning这样的单词 | 解决但不完美 | 自己动手解决问题 |
附加题中需要做数据展示 | 接触pandas、matplotlib、seaborn库,学习csv文件的格式 | 解决但不完美 | 初步接触python数据分析与展示 |
使用matplotlib时x轴标签会被挡住 | 查了matplotlib的文档以及多篇博客,尝试自己设置bottom属性,暂时只能通过手工调整来解决 | 未解决 | |
使用pyinstaller将python转成exe文件过程中报错 | 谷歌查了很多,还是未能解决这个问题。我成功用pyinstaller转成功了几个程序,只是我们附加题的py程序,暂时无法转成exe程序。 | 未解决 | 第一次知道python可以很方便的转成exe程序 |
评价我的队友
-
- 值得学习的地方:学习优异,性格和善严谨,在学习上非常努力。
个人学习进度条
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 200 | 200 | 10 | 10 | php学习 |
2 | 100 | 300 | 15 | 25 | 学习Axure RP的基本操作 |