結對第二次—文獻摘要熱詞統計及進階需求

所屬課程 福州大學軟件工程實踐(2019)
做業要求 結對第二次—文獻摘要熱詞統計及進階需求
結對學號 221600330吳可強、221600331向鵬
做業目標 提升我的編碼能力,查找資料與學習能力,團隊合做與溝通能力
本博客連接 http://www.javashuo.com/article/p-oasqqmbv-nm.html
隊友博客連接 http://www.javashuo.com/article/p-vdvakofs-nm.html
基本需求github地址 https://github.com/masgak/PairProject1-Java
進階需求github地址 https://github.com/masgak/PairProject2-Java

分工:

  • 一、221600330吳可強 :負責爬蟲與附加題部分,整合測試與修改函數,博客編寫。
  • 二、221600331向鵬:負責詞頻統計的代碼實現和相關撰寫,單元測試和性能分析。

本次做業分兩部分

一:基本需求

實現一個可以對文本文件中的單詞的詞頻進行統計的控制檯程序。
第一步、實現基本功能輸入文件名以命令行參數傳入。html

  • 一、統計文件的字符數
  • 二、統計文件的單詞總數
  • 三、統計文件的有效行數
  • 四、統計文件中各單詞的出現次數
  • 五、按照字典序輸出到文件result.txt

第二步、接口封裝java

  • 把基本功能裏的:統計字符數、統計單詞數、統計最多的10個單詞及其詞頻
    這三個功能獨立出來,成爲一個獨立的模塊

二:進階需求

新增功能,並在命令行程序中支持下述命令行參數。說明:字符總數統計、單詞總數統計、有效行統計要求與我的項目相同python

  • 一、使用工具爬取論文信息
  • 二、自定義輸入輸出文件
  • 三、加入權重的詞頻統計
  • 四、新增詞組詞頻統計功能
  • 五、自定義詞頻統計輸出
  • 六、多參數的混合使用

基本需求代碼簽入記錄:

進階需求代碼簽入記錄:

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

解題思路描述

爬蟲部分git

  • 工具:python3.7
  • 首先要求是爬取論文列表,而且將題目摘要等輸出到文件中。由於python是典型的爬取工具,代碼量較少邏輯較簡單所以咱們選擇這個語言。查看cvpr2018年論文網頁,發現論文基本詳情連接都在id=content class=ptitle中(爬取的連接只是完整連接後半部分),論文列表則存儲在content的表格中。

    點擊進入詳情頁就能夠直接看到需求中的題目與摘要都分別存儲在content下的id爲papertitle abstract中

    所以此次解題思路的流程圖以下:

    常見的python網頁解析工具備:re正則匹配、python自帶的html.parser模塊、第三方庫BeautifulSoup以及lxm庫。
    由於此次須要爬取的元素都直接代表了id與class,因此使用BeautifuiSoup能夠直接獲取信息,用其餘的解析器反而須要多寫判斷條件。

BeautifulSoup學習參考:http://www.javashuo.com/article/p-dxnsiaxg-c.htmlgithub

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,才能進行後續的詞頻與詞組頻率統計。echarts

內部實現設計(類圖)

關鍵算法(將單詞與頻率寫入map)流程圖

改進思路

  • 一、使用map存儲單詞
  • 二、使用bufferwriter發現重寫文件時flush次數太多,佔用大部分時間,因此改爲write
  • 三、判斷是否爲單詞時想使用正則表達式,但發現一個個字符直接判斷已經爲O(n),之後能夠再嘗試下

其中消耗最高的函數爲clear_String(),即去除文本中無關的ascii並重寫到新文件爲主要消耗。dom

代碼覆蓋率(分別爲單詞與詞組)

單元測試

代碼說明

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')
相關文章
相關標籤/搜索