过段时间要把以前的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分别列出了获取题目内容与标题的正则表达式:

 ojnum=;
cbox->AddString("bnu");
ojurl[ojnum]="http://www.bnuoj.com/bnuoj/problem_show.php?pid=";
ojgetstr[ojnum].allcontent="{<div id=\"showproblem\">(.|\n)*?<div id=\"one_content_base\">}";
ojgetstr[ojnum].title="<div id=\"showproblem\.*?<h1.*?>{.*?}</h1>";
ojgetstr[ojnum].iscode=true;
ojgetstr[ojnum].getnum=-;
ojgetstr[ojnum].pnostart=;
ojnum++; cbox->AddString("hdu");
ojurl[ojnum]="http://acm.hdu.edu.cn/showproblem.php?pid=";
ojgetstr[ojnum].allcontent="{<h1(.|\n)*?Note</a>}";
ojgetstr[ojnum].title="<h1.*?>{.*?}</h1>";
ojgetstr[ojnum].iscode=false;
ojgetstr[ojnum].getnum=-;
ojgetstr[ojnum].pnostart=;
ojnum++; cbox->AddString("neu");
ojurl[ojnum]="http://acm.nsu.edu.cn/JudgeOnline/problem.php?id=";
ojgetstr[ojnum].allcontent="{<div id=main(.|\n)*?Sample Output(.|\n)*?BBS(.|\n)*?</a>}";
ojgetstr[ojnum].title="<title.*?>{.*?}</title>";
//目前不需要把内容信息分开,就不正则详细的题目内容
/*ojgetstr[ojnum].tim="Time Limit:{.*?}&nbsp";
ojgetstr[ojnum].mem="Memory Limit:{.*?}<br>";
ojgetstr[ojnum].des=">Description</h2>.*?<div.*?>{.*?}</div>";
ojgetstr[ojnum].input=">Input</h2>.*?<div.*?>{.*?</div>.*?}</div>";
ojgetstr[ojnum].output=">Output</h2>.*?<div.*?>{.*?}</div>";
ojgetstr[ojnum].sinput=">Sample Input</h2>.*?{<pre>(.|\n)*?</pre>}";
ojgetstr[ojnum].soutput=">Sample Output</h2>.*?{<pre>(.|\n)*?</pre>}";*/
ojgetstr[ojnum].iscode=true;
ojgetstr[ojnum].getnum=-;
ojgetstr[ojnum].pnostart=;
ojnum++; cbox->AddString("poj");
ojurl[ojnum]="http://poj.org/problem?id=";
ojgetstr[ojnum].allcontent="{<div class=\"ptt\"(.|\n)*?Discuss</a>}";
ojgetstr[ojnum].title="<div class=\"ptt\".*?>{.*?}</div>";
ojgetstr[ojnum].iscode=true;
ojgetstr[ojnum].getnum=;//poj最多只允许短时间访问49道题
ojgetstr[ojnum].pnostart=;
ojnum++; cbox->AddString("vijos");
ojurl[ojnum]="https://vijos.org/p/";
ojgetstr[ojnum].allcontent="{<div class=\"pcontent\">(.|\n)*}<h4";
ojgetstr[ojnum].title="<div class=\"content\">.*?</span>{.*?}<div";
ojgetstr[ojnum].iscode=true;
ojgetstr[ojnum].getnum=-;
ojgetstr[ojnum].pnostart=;
ojnum++; cbox->AddString("zoj");
ojurl[ojnum]="http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=";
ojgetstr[ojnum].allcontent="{<div id=\"content_body\">(.|\n)*</div>}";
ojgetstr[ojnum].title="<div id=\"content_body\">.*?>.*?>{.*?}</span>";
ojgetstr[ojnum].iscode=false;
ojgetstr[ojnum].getnum=-;
ojgetstr[ojnum].pnostart=;
ojnum++;

因为不同的OJ网站所使用的编码格式不同,我使用的是ansi,所以要把一些使用utf-8的网页转换为ansi,下面是转换的代码:

static void  UTF8toANSI(CString &strUTF8)
{
UINT nLen = MultiByteToWideChar(CP_UTF8,NULL,strUTF8,-,NULL,NULL);
WCHAR *wszBuffer = new WCHAR[nLen+];
nLen = MultiByteToWideChar(CP_UTF8,NULL,strUTF8,-,wszBuffer,nLen);
wszBuffer[nLen] = ;
nLen = WideCharToMultiByte(,NULL,wszBuffer,-,NULL,NULL,NULL,NULL);
CHAR *szBuffer = new CHAR[nLen+];
nLen = WideCharToMultiByte(,NULL,wszBuffer,-,szBuffer,nLen,NULL,NULL);
szBuffer[nLen] = ; 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=;
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()<||data->title.GetLength()<)
{
return ;
}
//将处理后的数据写入到文件中
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 ;
}
}
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 ;
}
}
CArchive ar(&file,CArchive::store);
ar<<that->infonum;
for(int i=;i<that->infonum;i++)
{
ar<<(&that->problemarr[i]);
}
ar.Close();
file.Close(); //统计题目下载成功与失败
str.Format("SECC:%d FAIL:%d",that->infonum,totalnum--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题库下载

OnlineJudge 离线题库采集的更多相关文章

  1. 北大POJ题库使用指南

    原文地址:北大POJ题库使用指南 北大ACM题分类主流算法: 1.搜索 //回溯 2.DP(动态规划)//记忆化搜索 3.贪心 4.图论 //最短路径.最小生成树.网络流 5.数论 //组合数学(排列 ...

  2. 猿题库 iOS 客户端架构设计

    原文: http://mp.weixin.qq.com/s?__biz=MjM5NTIyNTUyMQ==&mid=444322139&idx=1&sn=c7bef4d439f4 ...

  3. NOI题库刷题日志 (贪心篇题解)

    这段时间在NOI题库上刷了刷题,来写点心得和题解 一.寻找平面上的极大点 2704:寻找平面上的极大点 总时间限制:  1000ms  内存限制:  65536kB 描述 在一个平面上,如果有两个点( ...

  4. NOI题库 1768最大子矩阵 题解

    NOI题库 1768最大子矩阵  题解     总时间限制: 1000ms 内存限制: 65536kB   描述   已知矩阵的大小定义为矩阵中所有元素的和.给定一个矩阵,你的任务是找到最大的非空(大 ...

  5. 猿题库 iOS 客户端架构设计-唐巧

    序 猿题库是一个拥有数千万用户的创业公司,从20013年题库项目起步到2015年,团队保持了极高的生产效率,使我们的产品完成了五个大版本和数十个小版本的高速迭代. 在如此快速的开发过程中,如何保证代码 ...

  6. NOI题库 09:图像旋转翻转变换

    NOI题库开始的题,也是略水,当然也是大水,所以彼此彼此 09:图像旋转翻转变换 总时间限制: 1000ms 内存限制: 65536kB 描述 给定m行n列的图像各像素点灰度值,对其依次进行一系列操作 ...

  7. NOI题库-小学奥赛QwQ

    今天Loli教育我们让我们来看看NOI题库的奥赛部分,不过,为何是小学的( ⊙ o ⊙ )啊!感觉智商被各种侮辱. 余数相同问题: 描述 已知三个正整数 a,b,c. 现有一个大于1的整数x,将其作为 ...

  8. 淘宝IP地址库采集器c#代码

    这篇文章主要介绍了淘宝IP地址库采集器c#代码,有需要的朋友可以参考一下. 最近做一个项目,功能类似于CNZZ站长统计功能,要求显示Ip所在的省份市区/提供商等信息.网上的Ip纯真数据库,下载下来一看 ...

  9. Hibernate考试试题(部分题库)含答案

    Hibernate考试试题 (题库) 1.  在Hibernate中,下列说法正确的有( ABC ).[选三项] A.Hibernate是一个开放源代码的对象关系映射框架 B.Hibernate对JD ...

随机推荐

  1. class 类(2)

    类属性和实例属性 一个类实例化后,实例是一个对象,有属性.同样,类也是一个对象,它也有属性. >>> class A(object): ... x = 7 ... >>& ...

  2. class 类(1)

    创建类 #!/usr/bin/env python # coding=utf-8 __metaclass__ = type class Person: def __init__(self, name) ...

  3. maven 打包源文件

    1.The source plugin can be used to create a jar file of the project sources from the command line or ...

  4. Fantageek翻译系列之《使用Autolayout显示变化高度的UITableViewCell》

    这篇博客主要在于,解释如何通过仅仅使用Autolayout很很少的代码,显示高度不同的Cell.虽然标题说的是TableView,但是CollectionView同样适合.但是,这种方法只使用iOS7 ...

  5. Mysql配置调优(转自阿铭论坛)

    Mysql配置文件my.cnf参数优化对于新手来讲,是比较难懂的东西,其实这个参数优化,是个很复杂的东西,对于不同的网站,及其在线量,访问量,帖子数量,网络情况,以及机器硬件配置都有关系,优化不可能一 ...

  6. 在cygwin下编译c语言

    #include <stdio.h> int main (void) { printf("Hello World!\n"); ; } 1.保存到cygwin工作目录下 ...

  7. <经验杂谈>查询表结构的SQL语句

    在我们使用SQL数据库的过程中,经常会遇到查询表结构的情况,以下就是sql语句的写法: --查询非系统数据库 SELECT name FROM Master..SysDatabases 查询数据库下所 ...

  8. (转)Javascript面向对象编程(二):构造函数的继承(作者:阮一峰)

    对象之间的"继承"的五种方法. 比如,现在有一个"动物"对象的构造函数. function Animal(){ this.species = "动物& ...

  9. WCF websocket

    WebSocket, like as TCP, is a bi-directional, full-duplex communication channel over a single TCP con ...

  10. (原).cc 和 .cpp 后缀结尾的文件的区别

    This caused a few problems the first time C++ was ported to a system where case wasn't significant i ...