1. 程式人生 > >軟工1816 · 第五次作業 - 結對作業2

軟工1816 · 第五次作業 - 結對作業2

open obj project 字符 分代 .html for += 下一個

一、Github地址 課程項目要求 隊友博客

二、具體分工

  • 031602225 林煌偉 :負責C++部分主要功能函數的編寫,算法的設計以及改進優化
  • 031602230 盧愷翔 : 爬蟲實現以及附加功能,代碼框架設計,接口封裝

三、psp表格 & 學習進度條

  • psp表格
PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 60 60
· Estimate · 估計這個任務需要多少時間 30 30
Development 開發 1000 1160
· Analysis · 需求分析 (包括學習新技術) 120 180
· Design Spec · 生成設計文檔 10 10
· Design Review · 設計復審 10 10
· Coding Standard · 代碼規範 (為目前的開發制定合適的規範) 10 10
· Design · 具體設計 30 30
· Coding · 具體編碼 780 900
· Code Review · 代碼復審 20 20
· Test · 測試(自我測試,修改代碼,提交修改) 20 20
Reporting 報告 130 130
· Test Repor · 測試報告 60 60
· Size Measurement · 計算工作量 10 10
· Postmortem & Process Improvement Plan · 事後總結, 並提出過程改進計劃 60 60
合計 1190 1350
  • 學習進度條
第N周 新增代碼(行) 累計代碼(行) 本周學習耗時(小時) 累計學習耗時(小時) 重要成長
1 500 500 12 12 單元測試的編寫
2 0 500 10 22 Axure原型設計工具的使用
3 500 1000 10 32 c++算法設計編寫能力,Debug調試能力

四、解題思路描述與設計實現說明 & 關鍵代碼解釋

  • 爬蟲使用

Attention!

爬蟲的CVPR.py文件放在CVPR\_Python的根目錄下。CVPR.exe文件放在
CVPR\_Python/dist/CVPR/CVPR.exe

,可以直接使用該文件來獲取論文信息。


本次我們使用Python編寫爬蟲,利用requests庫獲取網頁的html標簽樹,並用BeautifulSoup庫對其進行解析。利用正則表達式或者BeautifulSoup類對象獲取我們想要的信息。

#encoding: utf-8
import time
import requests
import multiprocessing
from multiprocessing import Queue,Process,Pool
from bs4 import BeautifulSoup
import re
import os
url = ‘http://openaccess.thecvf.com/CVPR2018.py‘
i = 0
href = Queue()
def get_url():
    try:
        r = requests.get(url,headers=req_header)
        r.raise_for_status()
        r.encoding = r.status_code
        bsObj = BeautifulSoup(r.text,‘lxml‘)
        for dt in bsObj.find_all(‘dt‘):
            new_url = ‘http://openaccess.thecvf.com/‘ + dt.a[‘href‘]
            href.put(new_url)
    except:
        print("error")
        

通過上述函數來獲取CVPR官網上所有的論文鏈接,並將他們放在一個隊列中。每次獲取論文的詳細信息時,就從隊列中拿出一個鏈接進行分析。


def get_message(myurl):
    try:
        global i

        title, abstract, authors, pdflink = get_abstract(myurl)
        with open(‘result.txt‘,‘a+‘,encoding=‘utf-8‘) as f:
            f.write(str(i) + ‘\n‘)
            f.write(‘Title: ‘ + title + ‘\n‘)
            f.write(‘Authors: ‘ + authors + ‘\n‘)
            f.write(‘Abstract: ‘ + abstract + ‘\n‘)
            f.write(‘PDF_LINK: ‘+ pdflink + ‘\n‘)
            f.write(‘\n\n‘)
        f.close()
        i += 1
        print("%s is crawled!" % myurl)
    except:
        print(‘error‘)

def get_abstract(newurl):
    try:
        r = requests.get(newurl,headers=req_header)
        r.raise_for_status()
        bsObj = BeautifulSoup(r.text,‘lxml‘)
        pattern = re.compile(‘(.*)</div>‘)
        title = re.findall(pattern, r.text)
        title = bsObj.find_all(‘div‘,{‘id‘:‘papertitle‘})[0].text
        abstract = bsObj.find_all(‘div‘,{‘id‘:‘abstract‘})[0].text
        pattern = re.compile(‘.*<i>(.*)</i>‘)
        authors = re.findall(pattern, r.text)[0]
        pattern = re.compile(‘.*<a href="(.*)">pdf‘)
        pdflink = re.findall(pattern,r.text)[0].replace(‘../..‘,‘http://openaccess.thecvf.com‘)
        return title.replace(‘\n‘,‘‘), abstract.replace(‘\n‘,‘‘), authors, pdflink
    except:
        print("error")

利用正則表達式BeautifulSoup類對象來獲取網頁中我們需要的信息,如論文標題、作者、簡介和論文的pdf鏈接。並將他們按照要求格式輸入至result.txt之中。


if __name__ == ‘__main__‘:
    if os.path.exists(‘result.txt‘):
        os.remove(‘result.txt‘)
    get_url()
    start = time.time()
    while not href.empty():
        get_message(href.get())
    # process = []
    # num_cpus = multiprocessing.cpu_count()
    # print(‘將會啟動進程數為:‘, num_cpus)
    # while not href.empty():
    #     process.append(href.get())
    # p = Pool()
    # p.map(get_message, process)
    # p.close()
    # p.join()
    end = time.time()
    print("共計用時%.4f秒" %(end-start))

本來這裏我想要用多進程來完成本次的爬蟲,但是遇到2個問題,所以放棄了。

  1. 在使用多進程的時候,我的論文編號會保持在1個數字不動,直到所有進程全部完成,才會將論文編號+1 。
  2. 在使用pyinstaller對.py文件編譯後的.exe文件,他會無限制地占用CPU資源,使得電腦死機。

希望如果懂得這些原理的大佬能夠幫忙評論一下ORZ

  • 代碼組織與內部實現設計(類圖)

技術分享圖片

  • 說明算法的關鍵與關鍵實現部分流程圖

  1. 針對論文的格式,遍歷一遍,提取出要求統計的字符,即 Title:Abstract: 後面的所有字符;並且分成兩個字符串string title 和 string abstract,以方便後面的權重分開計算
void MyCount::TitleAndAbstract(string filename)
{
    char ch;
    ifstream fin;
    fin.open(filename);//打開文件

    if (!fin)
    {
        cout << "can not open file" << endl;
    }

    while (!fin.eof())
    {
        ch = fin.get();
        if (ch == ‘\n‘)
        {
            ch = fin.get();
            if (ch == ‘T‘)
            {
                for (int i = 0; i <= 6; i++)
                    ch = fin.get();

                while (ch != ‘\n‘)
                {
                    title_str += ch;
                    ch = fin.get();
                }
                title_str += ch;
            }
        
        }
        if (ch == ‘\n‘)
        {
            ch = fin.get();
            if (ch == ‘A‘)
            {
                for (int i = 0; i < 10; i++)
                    ch = fin.get();

                while (ch != ‘\n‘ && !fin.eof())
                {
                    abstract_str += ch;
                    ch = fin.get();
                }
                if (ch == ‘\n‘)
                    abstract_str += ch;
            }

        }

    }
    fin.close();
}

2.統計有效行數,字符數,以及單詞數的算法思路和個人項目的相同,只不過給定的參數從文件名變成 title + abstract 的字符串,因為要去掉論文格式中不需要統計的部分;具體詳見詞頻統計作業

3.詞組統計思路:

  • 將title字符串與abstract字符串傳入函數,各自遍歷一遍,識別出 單詞+分隔符 的格式存入一個string數組 ci[] 中,並且換行符單獨存;
for (int i = 0; i < le; i++)//存入所有的詞和字符
    {

        if ((u[i] >= ‘a‘&&u[i] <= ‘z‘) || (u[i] >= ‘A‘&&u[i] <= ‘Z‘) || (u[i] >= ‘0‘&&u[i] <= ‘9‘))//第一個是字母或數字
        {

            while ((u[i] >= ‘a‘&&u[i] <= ‘z‘) || (u[i] >= ‘A‘&&u[i] <= ‘Z‘) || (u[i] >= ‘0‘&&u[i] <= ‘9‘))
            {
                if (u[i] >= ‘A‘&&u[i] <= ‘Z‘)
                    u[i] += 32;
                ci[k] += u[i];
                i++;
            }
            while (!(u[i] >= ‘a‘&&u[i] <= ‘z‘) && !(u[i] >= ‘A‘&&u[i] <= ‘Z‘) && !(u[i] >= ‘0‘&&u[i] <= ‘9‘) && u[i] != ‘\n‘&&i < le)
            {
                ci[k] += u[i];
                i++;
            }
            if (u[i] == ‘\n‘)ci[++k] = ‘\n‘;//換行符單獨存

            i--;
            k++;
        }
        else continue;
    }
  • 從頭開始遍歷 ci[] 組,以m為個數將符合單詞規範的詞連接起來,存入一個臨時字符串word中,如m=3,則判斷ci[0],ci[1],ci[2]是否為 合法單詞+分隔符,是的話存入word,不是的話跳到該詞的下一個繼續遍歷;
for (int i = 0; i < k; i++)
    {
        int flag1 = 0;
        int flag2 = 0;

        for (int j = 0; j < 4; j++)
        {
            if (!(ci[i][j] >= ‘a‘&& ci[i][j] <= ‘z‘))
            {
                flag2 = 1;//判斷第一個是否符合單詞規範
                break;
            }
        }

        if (flag2 == 0)//第一個符合
        {
            strcpy_s(temp_cizu.word, 500,ci[i].c_str()); //
            temp_cizu.count = 1;
            if (w == 1)temp_cizu.count += 9;
            for (int j = 1; j < m; j++)
            {
                int flag3 = 0;
                for (int f = 0; f < 4; f++)
                {
                    if (!(ci[i + j][f] >= ‘a‘&& ci[i + j][f] <= ‘z‘))
                    {
                        flag3 = 1;//判斷後幾個是否符合單詞規範
                        i = i + j;
                        break;
                    }
                }

                if (flag3 == 0)//後幾個都合法
                    strcat_s(temp_cizu.word, 500, ci[i + j].c_str());//
                else { flag1 = 1; break; }
            }
  • 將該臨時字符串word的結尾的分隔符去掉,就是一個合法的需要統計的詞組 ,拿去與合法詞組一一對比,相同的權重個數(位於Title:中的權重加10)累加,都不相同則新增詞組;
if (flag1 == 0) //插入
            {
                int a = strlen(temp_cizu.word) - 1;//去掉結尾的符號

                while (!(temp_cizu.word[a] >= ‘a‘&&temp_cizu.word[a] <= ‘z‘) && !(temp_cizu.word[a] >= ‘0‘&&temp_cizu.word[a] <= ‘9‘))
                {

                    temp_cizu.word[a]=‘\0‘;//
                    a--;
                }

                int h, j = t;
                for (h = 0; h < j; h++)//相同的詞個數累加
                {
                    if (strcmp(temp_cizu.word,cizu[h].word) == 0)//

                    {
                        cizu[h].count++;
                        if (w == 1)cizu[h].count += 9;
                        break;
                    }
                }

                if (t == 0 || h == j)//新詞生成新的空間
                {
                    cizu[t] = temp_cizu;
                    t++;
                }
            }
  • 將合法的詞組放入優先隊列中,自定義以頻率數統計,相同的按字典序排列, 輸出前n個詞組及權重值
bool operator< (wo a, wo b)//自定義排序
{
    //個數相同的單詞按字典排序
    if (a.count == b.count)
    {
        int i = -1;

        do {
            i++;
        } while (a.word[i] == b.word[i]);

        return a.word[i] > b.word[i];

    }
    //出現頻率排序    
    else return a.count < b.count;
}

算法思路圖如下:

技術分享圖片

4.接口參數
在命令行參數中如果遇到-iargv[i+1]是輸入文件名input_filename,同理-o對應的輸出文件名output_filename-w權重參數weight-m詞組詞頻統計參數m-n詞頻統計輸出參數n

stringstream ss;
string input_filename, output_filename;
int weight = 0;
int m = 1, n = 10;
for (int i = 1; i < argc; i++)
{
    string s;
    s = argv[i];
    if (s == "-i")input_filename = argv[i + 1];
    if (s == "-o")output_filename = argv[i + 1];
    if (s == "-m")
    {
        s = argv[i + 1];
        stringstream ss(s);
        ss >> m;
    }
    if (s == "-n")
    {
        s = argv[i + 1];
        stringstream ss(s);
        ss >> n;
    }
    if (s == "-w")
    {
        s = argv[i + 1];
        stringstream ss(s);
        ss >> weight;
    }
}

五、附加題設計與展示

  • 將論文中的作者和pdf鏈接爬取到result.txt中

技術分享圖片

六、性能分析與改進

用CVPR2018的979篇paper作為輸入數據,參數 -i 1.txt -m 3 -n 10 -w 1 -o output.txt
運行完發現耗時四分鐘多!我的天!調出查看性能分析:

技術分享圖片

發現主要的耗時問題:兩個string類型對比是否相同耗時達84%,想到之前個人項目的算法中是比較兩個char數組,耗時很少,於是將string類型改為 char數組類型,運行完發現只需20秒

結果如下:

技術分享圖片

分析報告如下:

技術分享圖片

技術分享圖片

最耗時的是count函數,不過改進之後縮短了很多時間

利用插件OpenCppCoverage,查看代碼覆蓋率:高達99%

技術分享圖片

七、單元測試

部分代碼如下:

#include "stdafx.h"
#include "CppUnitTest.h"
#include "../WordCount/CountAscii.h"
#include "../WordCount/CountWords.h"
#include "../WordCount/File.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace CountAsciiTest        //用於對字符數量的統計
{
    TEST_CLASS(UnitTest1)
    {
    public:

        TEST_METHOD(TestMethod1)
        {
            // TODO: 在此輸入測試代碼
            Ascii test_ascii;
            int count = test_ascii.countAscii("countascii.txt");
            Assert::IsTrue(count == 28);
        }

    };
}
namespace CountLineTest         //用於測試對空白行的技術
{
    TEST_CLASS(UnitTest1)
    {
    public:

        TEST_METHOD(TestMethod1)
        {
            // TODO: 在此輸入測試代碼
            Ascii test_ascii;
            int count = test_ascii.countLine("countline.txt");
            Assert::IsTrue(count == 0);
        }

    };
}

namespace FileTest      //用於對空文件名的測試
{
    TEST_CLASS(UnitTest1)
    {
    public:

        TEST_METHOD(TestMethod1)
        {
            // TODO: 在此輸入測試代碼
            Ascii test_ascii;
            int count = test_ascii.countAscii("1.txt");
            Assert::IsTrue(count == 0);
        }

    };
}

八、Github的代碼簽入記錄

技術分享圖片

九、遇到的代碼模塊異常或結對困難及解決方法

遇到的主要問題還是詞組統計的功能實現,為了確保正確率,花了挺多的時間設計算法,在草稿紙上寫了很多遍思路及其實現代碼

技術分享圖片

最後成功實現功能還是挺有成就感的,收獲就是鍛煉了自己的代碼編寫能力和算法優化能力,體會到了結對編程的過程,知道了怎麽提高合作的效率,怎麽更好的分工與整合。

十、評價你的隊友

  • 對盧愷翔的評價:
    由於是舍友,所以討論起來很方便,有什麽問題提出來都能及時得到解決,合作的效率非常高

  • 值得學習的地方
    太多了,比如對代碼要求嚴謹規範,我寫的有點亂,參數命名不合理,遭到吐槽,都是靠隊友後期規範化,向他學習以後也盡量寫得規範些

  • 需要改進的地方
    基本沒有

軟工1816 · 第五次作業 - 結對作業2