• OnlineJudge 离线题库采集


      过段时间要把以前的OJ换掉,我负责VirtualJudge的部分。需要用C与PHP写一个Linux下的VJudge。

        在此之前,将以前写给自己学弟学妹用的OJ离线题库的采集程序改进了一下。支持国内一些知名高校的OJ,为之后VJudge的开发练练手,熟悉下各个OJ的结构,免去以后再在LINUX上进行一些繁琐的测试。

        题目的采集没有使用任何OJ的API,直接采取从HTML页面采集数据并处理的方式。下载HTTP文件使用的是WinINet函数集,用起来比CURL还方便。正则表达式使用的ATL库里的regex。题目的储存使用的文件进行储存。别看一个OJ就有几千道题,文件结构访问的速度却是相当地快。因为抓取出来的题目有大量的HTML标签,先要全部去掉非常地困难(一些题目有不同的格式与链接标签,甚至有一些题目是关于标记语言的题目)。因为我现在做的是离线的题库,于是将这些标签保留写来,利用HTML的方式显示我采集的题目。

     

        目前做了六个OJ:

        1、bnu(北京师范大学OJ):这个OJ我觉得是国内用户体验做得最好的一个OJ,自己OJ的题目很丰富(含有较多不错的中文),VOJ的功能也完美地融合在自己的OJ中。

        2、hdu(杭州电子科技大学OJ):拥有国内最强判题机群,国内各大算法竞赛都选在杭电OJ举行。

        3、neu(成都东软学院OJ):很多人可能都不知道这OJ,也是国内少数登录需要验证码的奇葩OJ,以至于VOJ想支持它都困难,简单题爆多,欢迎来撸。我母校嘛,必须有,虽然我亚洲区牌都没拿到个,学校最高也就个铜。补习班性质的比赛嘛,二本学校的学生都不想把耍的时间花在觉得苦逼的事情上,我能拿着塑料坚持那么久也是NB了。该OJ即将在今年更换新的系统,判题内核、数据管理、界面、以及VOJ都由我们的老成员完成。希望以后学校的骚年能取得好成绩--wchrt。

        4、poj(北京大学OJ):搞ICPC的骚年都知道,国内最为知名的OJ之一。

        5、vijos(高效信息学在线评测系OJ):里面全是中文题哦,小心有大量的小学生,分分钟虐爆你的小学生。

        6、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">(.|
    )*?<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(.|
    )*?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(.|
    )*?Sample Output(.|
    )*?BBS(.|
    )*?</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>(.|
    )*?</pre>}";
    31     ojgetstr[ojnum].soutput=">Sample Output</h2>.*?{<pre>(.|
    )*?</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"(.|
    )*?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">(.|
    )*}<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">(.|
    )*</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和标题都提取出来,放到一个文件中记录,用于题目的引索。

    引索的结构是:题目数量、id1、标题1、id2、标题2、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题库下载

  • 相关阅读:
    SQL 游标使用实例 no
    C# DataTable 转换成JSON数据 no
    css设置滚动条的样式 no
    C# DataTable 转换成JSON数据 no
    springboot项目打包jar 并打包为exe启动
    springboot 项目启动自动打开浏览器访问网站设置
    springboot启动创建系统托盘及功能
    关于Web Service
    最近的我
    C++ wstring和string相互转换
  • 原文地址:https://www.cnblogs.com/wchrt/p/4236034.html
Copyright © 2020-2023  润新知