所屬課程 | 福州大學軟體工程實踐(2019) |
---|---|
作業要求 | 結對第二次—文獻摘要熱詞統計及進階需求 |
結對學號 | 221600330吳可強、221600331向鵬 |
作業目標 | 提高個人編碼能力,查詢資料與學習能力,團隊合作與溝通能力 |
本部落格連結 | https://www.cnblogs.com/xiang-peng/p/10540046.html |
隊友部落格連結 | https://www.cnblogs.com/masgak/p/10537632.html |
基本需求github地址 | https://github.com/Snug-XP/PairProject1-Java |
進階需求github地址 | https://github.com/masgak/PairProject2-Java |
分工:
- 1、221600330吳可強 :負責爬蟲與附加題部分,整合測試與修改函式,部落格編寫。
- 2、221600331向鵬:負責詞頻統計的程式碼實現和相關撰寫,單元測試和效能分析。
本次作業分兩部分
一:基本需求
實現一個能夠對文字檔案中的單詞的詞頻進行統計的控制檯程式。
第一步、實現基本功能輸入檔名以命令列引數傳入。
- 1、統計檔案的字元數
- 2、統計檔案的單詞總數
- 3、統計檔案的有效行數
- 4、統計檔案中各單詞的出現次數
- 5、按照字典序輸出到檔案result.txt
第二步、介面封裝
- 把基本功能裡的:統計字元數、統計單詞數、統計最多的10個單詞及其詞頻
這三個功能獨立出來,成為一個獨立的模組
二:進階需求
新增功能,並在命令列程式中支援下述命令列引數。說明:字元總數統計、單詞總數統計、有效行統計要求與個人專案相同
- 1、使用工具爬取論文資訊
- 2、自定義輸入輸出檔案
- 3、加入權重的詞頻統計
- 4、新增片語詞頻統計功能
- 5、自定義詞頻統計輸出
- 6、多引數的混合使用
基本需求程式碼簽入記錄:
進階需求程式碼簽入記錄:
PSP表格
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 20 | 45 |
? Estimate | ? 估計這個任務需要多少時間 | 20 | 45 |
Development | 開發 | 720 | 900 |
? Analysis | ? 需求分析 (包括學習新技術) | 60 | 30 |
? Design Spec | ? 生成設計文件 | 0 | 0 |
? Design Review | ? 設計複審 | 120 | 100 |
? Coding Standard | ? 程式碼規範 (為目前的開發制定合適的規範) | 0 | 0 |
? Design | ? 具體設計 | 60 | 70 |
? Coding | ? 具體編碼 | 720 | 900 |
? Code Review | ? 程式碼複審 | 300 | 240 |
? Test | ? 測試(自我測試,修改程式碼,提交修改) | 60 | 90 |
Reporting | 報告 | 180 | 240 |
? Test Report | ? 測試報告 | 0 | 0 |
? Size Measurement | ? 計算工作量 | 0 | 0 |
? Postmortem & Process Improvement Plan | ? 事後總結, 並提出過程改進計劃 | 30 | 50 |
合計 | 2270 | 2710 |
解題思路描述
爬蟲部分
- 工具:python3.7
- 首先要求是爬取論文列表,並且將題目摘要等輸出到檔案中。因為python是典型的爬取工具,程式碼量較少邏輯較簡單因此我們選擇這個語言。檢視cvpr2018年論文網頁,發現論文基本詳情連結都在id=content class=ptitle中(爬取的連結只是完整連結後半部分),論文列表則儲存在content的表格中。
點選進入詳情頁就可以直接看到需求中的題目與摘要都分別儲存在content下的id為papertitle abstract中
因此這次解題思路的流程圖如下:
常見的python網頁解析工具有:re正則匹配、python自帶的html.parser模組、第三方庫BeautifulSoup以及lxm庫。
因為這次需要爬取的元素都直接表明了id與class,所以使用BeautifuiSoup可以直接獲取資訊,用其他的解析器反而需要多寫判斷條件。
BeautifulSoup學習參考:https://www.cnblogs.com/hanmk/p/8724162.html
java部分
- 一開始看到題目後覺得題目好繁雜,看了好多遍才大致抓住重點,又結合群裡面的討論又注意到一些原本被我忽略的細節。看到基本要求覺得還是很簡單的,一個個小功能本來一開始也是打算在一次讀取檔案時實現,之後發現有點不方便,加上看到把各個功能獨立出來的要求,就分開實現了。在進階要求中,可以直接使用前面完成的程式碼,把論文爬取結果的檔案去掉不需要統計的部分形成一個新檔案,用這個新檔案統計字元數、單詞數和行數,而單詞和片語的權重詞頻就用原檔案統計。
設計實踐過程
程式碼組織:
221600330&221600331
|- src
|- Main.java(主程式,可以從命令列接收引數)
|- lib.java(包括所有的統計字元,行數,單詞數等程式)
|- file.java(與檔案流有關的程式)
|- cvpr
|- result.txt(爬蟲結果)
|- cvpr.python(爬蟲程式,可以爬取CVPR2018論文列表)
基礎需求有兩個類,主類接收命令引數,lib類包括基本的統計函式
進階需求有三個類,主類接收命令引數,file類包括的函式有
public static String clear_String() //去除字串中的非ASCII碼
public static String rewrite_Txt()//去掉爬取結果中例如“Title: ”、“Abstract: ”、換行符等並寫入一個新檔案
public static void writeToFile()//寫入檔案
lib類包括的函式有
public static int count_Lines()//統計行數
public static int get_Words_Num()//統計單詞數
public static Map<String, String> count_Words()//將單詞與出現次數放入map中
public static int count_Characters()//統計字元數
public static Map<String, String> count_Phrase_frequency()//計算片語詞頻
public static Map<String, String> count_Word_Frequency()//計算單詞詞頻
其中統計片語與單詞詞頻函式與count_Words息息相關,這兩函式的輸入結果就有上個函式形成的雜湊表。只有先將單詞與出現次數形成一個map,才能進行後續的詞頻與片語頻率統計。
內部實現設計(類圖)
關鍵演算法(將單詞與頻率寫入map)流程圖
改進思路
- 1、使用map儲存單詞
- 2、使用bufferwriter發現重寫檔案時flush次數太多,佔用大部分時間,所以改成write
- 3、判斷是否為單詞時想使用正規表示式,但發現一個個字元直接判斷已經為O(n),以後可以再嘗試下
其中消耗最高的函式為clear_String(),即去除文字中無關的ascii並重寫到新檔案為主要消耗。
程式碼覆蓋率(分別為單詞與片語)
單元測試
程式碼說明
python:爬取詳情頁連結並迴圈生成新連結
cvprurl='http://openaccess.thecvf.com/CVPR2018.py'
response=requests.get(cvprurl)
soup=BeautifulSoup(response.text,'html.parser')
for links in soup.select('.ptitle'): #返回每個超連結
newlinks = 'http://openaccess.thecvf.com/'+ links.select('a')[0]['href']
python:在新連線中爬取標題與摘要並寫入文件
response2 = requests.get(newlinks) #開啟連結
soup2 = BeautifulSoup(response2.text,'html.parser') #把網頁內容存進容器
Title = soup2.select('#papertitle')[0].text.strip() #找到標題元素爬取
print("Title:",Title,file=txt)
java:首先按行讀取檔案,再判斷讀取到的流是否為單詞(至少以4個英文字母開頭,跟上字母數字符號,單詞以分隔符分割,不區分大小寫),如果是單詞則讀入hashmap中,每多讀一次出現次數加一,形成最初的map資料。
public static Map<String, String> count_Words(String file_path){
......................
while ((str = bufferedReader.readLine()) != null) {
str = str.toLowerCase();
for (int i = 0; i < (str.length() - 3); i++) {
if (i > 0) {
if (('a' <= str.charAt(i - 1) && str.charAt(i - 1) <= 'z')
|| (48 <= str.charAt(i - 1) && str.charAt(i - 1) <= 57)) {// 如果前一個字元是字元或數字
continue;
}
}
if ('a' <= str.charAt(i) && str.charAt(i) <= 'z') {
if ('a' <= str.charAt(i + 1) && str.charAt(i + 1) <= 'z') {
if ('a' <= str.charAt(i + 2) && str.charAt(i + 2) <= 'z') {
if ('a' <= str.charAt(i + 3) && str.charAt(i + 3) <= 'z') {// 找到單詞
int j;
for (j = i + 4; j < str.length(); j++) {// 看單詞是否結束
if ('a' > str.charAt(j) || str.charAt(j) > 'z') {
if (48 > str.charAt(j) || str.charAt(j) > 57)// 不是數字
break;
}
}
String temp = str.substring(i, j);// 擷取字串索引號i到j區域(包括i,不包括j)
// 加到Map裡去
if (map.containsKey(temp)) {
int n = Integer.parseInt(map.get(temp));
n++;
map.put(temp, n + "");
} else
map.put(temp, "1");
該過程的流程圖為:
java:片語頻率,在判斷單詞的基礎上加個計數器計算連續出現的單詞,放入字串陣列,如果次數大於片語長度,每判斷一個單詞就從字串陣列中該單詞前M個組成個片語。
public static Map<String, String> count_Phrase_frequency(String file_path, int phraseLength, boolean is_weight)
....................................
while ((str = bufferedReader.readLine()) != null) {
str = file.clear_String(str);
str = str.toLowerCase();
// 去掉"title: "和"abstract: "
if (str.contains("title: ")) {
is_title = true;
str = str.substring(0, str.indexOf("title: ")) + str.substring(str.indexOf("title: ") + 7);
} else
is_title = false;
if (str.contains("abstract: "))
str = str.substring(0, str.indexOf("abstract: ")) + str.substring(str.indexOf("abstract: ") + 10);
int count = 0;// 計算連續出現的單詞數
String[] words = new String[101];// 儲存連續出現的單詞
for (int i = 0; i < (str.length() - 3); i++) {
if (i > 0) {
if (('a' <= str.charAt(i - 1) && str.charAt(i - 1) <= 'z')
|| (48 <= str.charAt(i - 1) && str.charAt(i - 1) <= 57)) {// 如果前一個字元是字元或數字
continue;
}
}
if ('a' <= str.charAt(i) && str.charAt(i) <= 'z') {
if ('a' <= str.charAt(i + 1) && str.charAt(i + 1) <= 'z') {
if ('a' <= str.charAt(i + 2) && str.charAt(i + 2) <= 'z') {
if ('a' <= str.charAt(i + 3) && str.charAt(i + 3) <= 'z') {// 找到一個單詞
int j;
for (j = i + 4; j < str.length(); j++) {// 看單詞是否結束
if ('a' > str.charAt(j) || str.charAt(j) > 'z') {
if (48 > str.charAt(j) || str.charAt(j) > 57)// 不是數字,遇到分隔符
break;
}
}
String temp = str.substring(i, j);// 擷取字串索引號i到j區域(包括i,不包括j+1)---擷取單詞
if (j == str.length())// 一段中以單詞結尾
temp = temp + " ";
else
temp = temp + str.charAt(j);// 把單詞後面的一個分隔符加到單詞中去
count++;
words[count] = temp;
if (count >= phraseLength) {
temp = words[count - phraseLength + 1];
for (int k = phraseLength; k > 1; k--) {
temp = temp + words[count - k + 2];
}
temp = temp.substring(0, temp.length() - 1);// 和並後去掉末尾的分割符
// 加到Map裡去
if (is_weight && is_title)// 計算權值為10:1,並且該片語在title段中
{
if (map.containsKey(temp)) {
int n = Integer.parseInt(map.get(temp));
n += 10;
map.put(temp, n + "");
} else
map.put(temp, "10");
} else {
// 不需要計算權值
if (map.containsKey(temp)) {
int n = Integer.parseInt(map.get(temp));
n++;
map.put(temp, n + "");
} else
map.put(temp, "1");
}
int n = Integer.parseInt(map.get("count_words_num"));
n++;// 總片語個數加一
map.put("count_words_num", n + "");
}
i = j;
} else {
count = 0;
i = i + 3;
} // 遇到少於4個字母的單詞,結束count
} else {
count = 0;
i = i + 2;
} // 遇到少於4個字母的單詞,結束count
} else {
count = 0;
i = i + 1;
} // 遇到少於4個字母的單詞,結束count
} else {
if ((48 > str.charAt(i) || str.charAt(i) > 57)) {// 遇到分隔符加到加到上一個單詞末尾
words[count] += str.charAt(i);
} else {
// 遇到數字,結束count
count = 0;
}
遇到的程式碼模組異常或結對困難及解決方法
- 讀入文字是亂碼,特別是經過多個編譯器與複製下載過程,常常換個編譯器註釋就會亂碼。
- 字元數統計出錯,由於對python使用不熟練,導致爬下資料中其他不可見字元沒有清除乾淨。統計字元常常會多幾個出來,找了很久才發現這個問題。
- 電腦java編譯環境沒有安裝好,導致無法直接編譯執行.java檔案。
解決辦法
- 一律使用同個編譯器,或者用系統的編輯器
- 查詢python去除不可見字元的方法,在此之前用別人的資料測試
- 先在eclipse中執行得到.class檔案,再用cmd執行。後續再重灌java環境
評價你的隊友
- 221600330:隨叫隨到,查資料能力強,學習效率高並樂於嘗試、善於交流 需要改進:太佛性
- 221600331:善於思考,做事態度細心負責,寫程式碼時非常拼,有堅韌不拔的態度。 需要改進:容易鑽牛角尖
附加題設計與展示
創意獨到之處
- 1,在爬取Title, abstract的基礎上又爬取了頁面上能找到的作者,期刊名,時間,pdf連結
- 2,各熱詞出現的餅圖概率
爬取結果與餅圖檔案:https://files-cdn.cnblogs.com/files/masgak/Desktop.rar
實現思路
- 1,繼續觀察論文詳情頁的資訊可以發現在content下的class=bibref中包括了所有資訊,例如無法直接看出來的期刊名稱,月份等資訊。
因為標題與摘要資訊較為簡單,前面可以使用自帶的html.parser解析器直接獲取,但這個資訊使用html.parser更加複雜,因此選擇用lxml解析器得到一個二維陣列,再從二維陣列中得到biber上的所有資訊。
爬取結果截圖:
- 2,使用從wordcount中得到的十大熱詞數量,結合python的pyecharts畫出餅圖
餅圖:
pyecharts程式碼
# coding=utf-8
from pyecharts import Pie
import random
attr = ["that","with","this","image", "from", "learning", "network", "which", "model","images"]
v1 = [1755, 1473, 1443, 1223, 1096, 971,971,807,760,732]
pie = Pie("熱詞出現次數")
pie.add("", attr, v1, is_label_show=True)
pie.render('pie.html')