OnlineJudge 離線題庫採集

  過段時間要把之前的OJ換掉,我負責VirtualJudge的部分。須要用C與PHP寫一個Linux下的VJudge。php

    在此以前,將之前寫給本身學弟學妹用的OJ離線題庫的採集程序改進了一下。支持國內一些知名高校的OJ,爲以後VJudge的開發練練手,熟悉下各個OJ的結構,免去之後再在LINUX上進行一些繁瑣的測試。html

    題目的採集沒有使用任何OJ的API,直接採起從HTML頁面採集數據並處理的方式。下載HTTP文件使用的是WinINet函數集,用起來比CURL還方便。正則表達式使用的ATL庫裏的regex。題目的儲存使用的文件進行儲存。別看一個OJ就有幾千道題,文件結構訪問的速度倒是至關地快。由於抓取出來的題目有大量的HTML標籤,先要所有去掉很是地困難(一些題目有不一樣的格式與連接標籤,甚至有一些題目是關於標記語言的題目)。由於我如今作的是離線的題庫,因而將這些標籤保留寫來,利用HTML的方式顯示我採集的題目。正則表達式

 

    目前作了六個OJ:算法

    一、bnu(北京師範大學OJ):這個OJ我以爲是國內用戶體驗作得最好的一個OJ,本身OJ的題目很豐富(含有較多不錯的中文),VOJ的功能也完美地融合在本身的OJ中。瀏覽器

    二、hdu(杭州電子科技大學OJ):擁有國內最強判題機羣,國內各大算法競賽都選在杭電OJ舉行。服務器

    三、neu(成都東軟學院OJ):不少人可能都不知道這OJ,也是國內少數登陸須要驗證碼的奇葩OJ,以致於VOJ想支持它都困難,簡單題爆多,歡迎來擼。我母校嘛,必須有,雖然我亞洲區牌都沒拿到個,學校最高也就個銅。補習班性質的比賽嘛,二本學校的學生都不想把耍的時間花在以爲苦逼的事情上,我能拿着塑料堅持那麼久也是NB了。該OJ即將在今年更換新的系統,判題內核、數據管理、界面、以及VOJ都由咱們的老成員完成。但願之後學校的騷年能取得好成績--wchrt。網絡

    四、poj(北京大學OJ):搞ICPC的騷年都知道,國內最爲知名的OJ之一。ide

    五、vijos(高效信息學在線評測系OJ):裏面全是中文題哦,當心有大量的小學生,分分鐘虐爆你的小學生。函數

    六、zoj(浙江大學OJ):國內起步最先的OJ之一,但我基本沒怎麼用過這個OJ。測試

 

    下面根據各oj分別列出了獲取題目內容與標題的正則表達式:

 1 ojnum=0;
 2     cbox->AddString("bnu");
 3     ojurl[ojnum]="http://www.bnuoj.com/bnuoj/problem_show.php?pid=";
 4     ojgetstr[ojnum].allcontent="{<div id=\"showproblem\">(.|\n)*?<div id=\"one_content_base\">}";
 5     ojgetstr[ojnum].title="<div id=\"showproblem\.*?<h1.*?>{.*?}</h1>";
 6     ojgetstr[ojnum].iscode=true;
 7     ojgetstr[ojnum].getnum=-1;
 8     ojgetstr[ojnum].pnostart=1000;
 9     ojnum++;
10  
11     cbox->AddString("hdu");
12     ojurl[ojnum]="http://acm.hdu.edu.cn/showproblem.php?pid=";
13     ojgetstr[ojnum].allcontent="{<h1(.|\n)*?Note</a>}";
14     ojgetstr[ojnum].title="<h1.*?>{.*?}</h1>";
15     ojgetstr[ojnum].iscode=false;
16     ojgetstr[ojnum].getnum=-1;
17     ojgetstr[ojnum].pnostart=1000;
18     ojnum++;
19  
20     cbox->AddString("neu");
21     ojurl[ojnum]="http://acm.nsu.edu.cn/JudgeOnline/problem.php?id=";
22     ojgetstr[ojnum].allcontent="{<div id=main(.|\n)*?Sample Output(.|\n)*?BBS(.|\n)*?</a>}";
23     ojgetstr[ojnum].title="<title.*?>{.*?}</title>";
24     //目前不須要把內容信息分開,就不正則詳細的題目內容
25     /*ojgetstr[ojnum].tim="Time Limit:{.*?}&nbsp";
26     ojgetstr[ojnum].mem="Memory Limit:{.*?}<br>";
27     ojgetstr[ojnum].des=">Description</h2>.*?<div.*?>{.*?}</div>";
28     ojgetstr[ojnum].input=">Input</h2>.*?<div.*?>{.*?</div>.*?}</div>";
29     ojgetstr[ojnum].output=">Output</h2>.*?<div.*?>{.*?}</div>";
30     ojgetstr[ojnum].sinput=">Sample Input</h2>.*?{<pre>(.|\n)*?</pre>}";
31     ojgetstr[ojnum].soutput=">Sample Output</h2>.*?{<pre>(.|\n)*?</pre>}";*/
32     ojgetstr[ojnum].iscode=true;
33     ojgetstr[ojnum].getnum=-1;
34     ojgetstr[ojnum].pnostart=1000;
35     ojnum++;
36  
37     cbox->AddString("poj");
38     ojurl[ojnum]="http://poj.org/problem?id=";
39     ojgetstr[ojnum].allcontent="{<div class=\"ptt\"(.|\n)*?Discuss</a>}";
40     ojgetstr[ojnum].title="<div class=\"ptt\".*?>{.*?}</div>";
41     ojgetstr[ojnum].iscode=true;
42     ojgetstr[ojnum].getnum=49;//poj最多隻容許短期訪問49道題
43     ojgetstr[ojnum].pnostart=1000;
44     ojnum++;
45  
46     cbox->AddString("vijos");
47     ojurl[ojnum]="https://vijos.org/p/";
48     ojgetstr[ojnum].allcontent="{<div class=\"pcontent\">(.|\n)*}<h4";
49     ojgetstr[ojnum].title="<div class=\"content\">.*?</span>{.*?}<div";
50     ojgetstr[ojnum].iscode=true;
51     ojgetstr[ojnum].getnum=-1;
52     ojgetstr[ojnum].pnostart=1000;
53     ojnum++;
54  
55     cbox->AddString("zoj");
56     ojurl[ojnum]="http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=";
57     ojgetstr[ojnum].allcontent="{<div id=\"content_body\">(.|\n)*</div>}";
58     ojgetstr[ojnum].title="<div id=\"content_body\">.*?>.*?>{.*?}</span>";
59     ojgetstr[ojnum].iscode=false;
60     ojgetstr[ojnum].getnum=-1;
61     ojgetstr[ojnum].pnostart=1000;
62     ojnum++;

 

由於不一樣的OJ網站所使用的編碼格式不一樣,我使用的是ansi,因此要把一些使用utf-8的網頁轉換爲ansi,下面是轉換的代碼:

static void  UTF8toANSI(CString &strUTF8)
{
    UINT nLen = MultiByteToWideChar(CP_UTF8,NULL,strUTF8,-1,NULL,NULL);
    WCHAR *wszBuffer = new WCHAR[nLen+1];
    nLen = MultiByteToWideChar(CP_UTF8,NULL,strUTF8,-1,wszBuffer,nLen);
    wszBuffer[nLen] = 0;
    nLen = WideCharToMultiByte(936,NULL,wszBuffer,-1,NULL,NULL,NULL,NULL);
    CHAR *szBuffer = new CHAR[nLen+1];
    nLen = WideCharToMultiByte(936,NULL,wszBuffer,-1,szBuffer,nLen,NULL,NULL);
    szBuffer[nLen] = 0;
 
    strUTF8 = szBuffer;
    delete []szBuffer;
    delete []wszBuffer;
}

使用WinINet函數集的CInternetSession與CHttpFile下載HTTP文件,那hdu做爲例子

hdu的1001題的地址是:"http://acm.hdu.edu.cn/showproblem.php?pid=1001"

這裏的http://acm.hdu.edu.cn/showproblem.php是hdu的題目PHP文件,咱們須要以GET的方式請求pid=1001的數據。對於hdu的其餘題目,咱們只需把pid的值設置成其餘的值便可。

//獲取GET方式獲取題目的HTML頁面
        CInternetSession intsess;
    CHttpFile *phtfile = NULL;
    phtfile = (CHttpFile *)intsess.OpenURL(url);
    UINT nfilelen = (UINT) phtfile->GetLength();
    CString strhtml;
    CString buffer;
    UINT dw=0;
    while(dw<nfilelen)//將數據轉入strhtml處理
    {
        dw+=phtfile->ReadString(buffer);
        strhtml+=buffer;
        dw++;
    }
     
    //使用正則表達式提取數須要的內容
    data->all=strhtml;
    getcontent(ojgetstr->allcontent,strhtml,data->allcontent);//去除題目頁面的多與信息
    getcontent(ojgetstr->title,strhtml,data->title);
    /*getcontent(ojgetstr->tim,strhtml,data->tim);
    getcontent(ojgetstr->mem,strhtml,data->mem);
    getcontent(ojgetstr->des,strhtml,data->des);
    getcontent(ojgetstr->input,strhtml,data->input);
    getcontent(ojgetstr->output,strhtml,data->output);
    getcontent(ojgetstr->sinput,strhtml,data->sinput);
    getcontent(ojgetstr->soutput,strhtml,data->soutput);*/
 
    if(ojgetstr->iscode)
    {
        UTF8toANSI(data->allcontent);
        UTF8toANSI(data->title);
        /*UTF8toANSI(data->tim);
        UTF8toANSI(data->mem);
        UTF8toANSI(data->des);
        UTF8toANSI(data->input);
        UTF8toANSI(data->output);
        UTF8toANSI(data->sinput);
        UTF8toANSI(data->soutput);*/
    }
 
    if(data->allcontent.GetLength()<5||data->title.GetLength()<1)
    {
        return 0;
    }
    //將處理後的數據寫入到文件中
    CFile file;
    if(!file.Open("c:\\ojdata\\"+data->oj+"\\"+data->id,CFile::modeWrite))
    {
        if(!file.Open("c:\\ojdata\\"+data->oj+"\\"+data->id,CFile::modeCreate|CFile::modeWrite))
        {
            return 0;
        }
    }
    CArchive ar(&file,CArchive::store);
    ar<<data;
    ar.Close();
    file.Close();

由於每一個OJ的題目都有幾千道,不可能每次打開軟件都去遍歷那麼幾千個文件,效率過低並且容易出錯。所以我將每一個題目的ID和標題都提取出來,放到一個文件中記錄,用於題目的引索。

引索的結構是:題目數量、id一、標題一、id二、標題二、id3...的格式,每一個OJ一個表。

下面是儲存記錄的代碼,使用序列化儲存:

CFile file;
    if(!file.Open("c:\\ojdata\\"+oj+"\\pinfo",CFile::modeWrite))
    {
        if(!file.Open("c:\\ojdata\\"+oj+"\\pinfo",CFile::modeCreate|CFile::modeWrite))
        {
            AfxMessageBox("寫入出錯");
            return 1;
        }
    }
    CArchive ar(&file,CArchive::store);
    ar<<that->infonum;
    for(int i=0;i<that->infonum;i++)
    {
        ar<<(&that->problemarr[i]);
    }
    ar.Close();
    file.Close();
 
    //統計題目下載成功與失敗
    str.Format("SECC:%d  FAIL:%d",that->infonum,totalnum-50-that->infonum);

hdu的:

全是中文題目的VIJOS,不錯不錯:

水水更健康:

基本上題目的採集工做到此結束。之後作VOJ的時候再將各個部分正則出來。

由於網絡環境的因素,OJ題目的訪問速度不必定很快,這裏能夠用開多個線程進行下載,以及優化題目的下載和處理,由於我能夠直接掛在個人服務器上下載,因此就一條線程下載處理完了。我用的電信50M的寬帶(坑爆,說是50M,上行只有不到2M,電信太煎餅了,一個月169還不包含短信費,80端口封完喊還推薦我開3000一個月的商務寬帶)大概下載一個OJ的全部題目10多分鐘,我直接6個OJ一塊兒下載的。不想等待的朋友能夠直接在附件下載我採集好的題目包,把它解壓到到C盤便可。

 

    採集到了題目,怎麼看呢?確定不能直接給人看,一堆標記語言煩死了。WINDOWS自帶了瀏覽器控件,直接使用它便可,我使用了兩種方式,一種是對話框上的HTML控件,多是個人緣由可是這個控件不穩定, 在一些電腦上老加載出錯。


    因而我另外用了CDHtmlDialog。這直接是個HTML的對話框,每次把題目的本地地址給他後讓其Navigate便可。因爲我題目是儲存的一個problemdata結構體,含有一些其餘的信息。打開題目前須要把要顯示的題目提取出來,從新生成一個HTML文件,而後再讓HTML對話框打開它。

題目的顯示過程:

bool ProblemList::opensafeproblem(CString oj,CString pid)
{
    CFile file;
    if(!file.Open("c:\\ojdata\\"+oj+"\\"+pid,CFile::modeRead))
    {
        return false;
    }
    CArchive ar(&file,CArchive::load);
    problemdata *data;
    ar>>data;
    ar.Close();
    file.Close();
 
 
    HtmlContent *contentdlg=new HtmlContent;
    if(!contentdlg->setdata(*data))
    {
        AfxMessageBox("文件打開失敗");
        return false;
    }
    contentdlg->Create(IDD_DIALOG_HTML);
    contentdlg->ShowWindow(SW_SHOW);
 
    contentdlg->SetWindowTextA(pid);
 
    return true;
}
BOOL HtmlContent::OnInitDialog()
{
    CDHtmlDialog::OnInitDialog();
 
    this->SetHostFlags(DOCHOSTUIFLAG_NO3DBORDER | DOCHOSTUIFLAG_FLAT_SCROLLBAR);
 
 
    //將題目轉化爲HTML文件並在本地打開顯示
    CFile *f=new CFile;
    if(!f->Open("c:\\ojdata\\"+data->oj+"\\"+data->id+".html",CFile::modeCreate|CFile::modeWrite))
    {
        AfxMessageBox("打開失敗");
        this->CloseWindow();
    }
    f->Write(data->allcontent,data->allcontent.GetLength());
    f->Close();
     
    this->Navigate("c:\\ojdata\\"+data->oj+"\\"+data->id+".html");
    return TRUE;  // 除非將焦點設置到控件,不然返回 TRUE
}

整個離線題庫就是這樣,須要支持其餘OJ只要改改正則和題號便可,對於一些無法直接獲取到題目的OJ要作另外的處理,uestc的新OJ就須要另外的方式獲取題目內容。

    採集程序源碼

    採集程序

    OJ題庫下載

相關文章
相關標籤/搜索