杭州电子科技大学的OJ(http://acm.hdu.edu.cn/)(以后简称杭电)很有特色,目前也很火,其中一个关键原因就是它提供了一些新功能,比如diy,webdiy,virtual contest等。这里我们简单讨论一下杭电的webdiy。
webdiy是什么?是在DIY的基础上增加了从其他OJ选题的功能,那么DIY是什么?就是自己在本地选题,然后组成一场比赛。实现这个功能关键是能在其他OJ上提交,并能获得评判结果,直接往数据库里面写肯定是不可能的,那就只剩下一个方法:网络爬虫,模拟用户提交。
最近一直在研究python的网络编程模块,用python来实现这个功能还是比较简单的,先看两张demo截图
左边是poj,右边是zoj,除了给出评判结果外,还有必要的提示信息
下面是代码实现:
POJ
# -*- coding: utf-8 -*-
import sys
import logging
from time import sleep
import urllib,urllib2,cookielib
from BeautifulSoup import BeautifulSoup
class POJ:
URL_HOME = \'http://poj.org/\'
URL_LOGIN = URL_HOME + \'login?\'
URL_SUBMIT = URL_HOME + \'submit?\'
URL_STATUS = URL_HOME + \'status?\'
#结果信息
INFO =[\'RunID\',\'User\',\'Problem\',\'Result\',\'Memory\',\'Time\',\'Language\',\'Code Length\',\'Submit Time\']
#语言
LANGUAGE = {
\'G++\':\'0\',
\'GCC\':\'1\',
\'JAVA\':\'2\',
\'PASCAL\':\'3\',
\'C++\':\'4\',
\'C\':\'5\',
\'FORTRAN\':\'6\',
}
def __init__(self, user_id, password):
self.user_id = user_id
self.password = password
cj = cookielib.LWPCookieJar()
self.opener =urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(self.opener)
def login(self):
data = dict(
user_id1 = self.user_id,
password1 = self.password,
B1 = \'login\',
url = \'.\')
postdata = urllib.urlencode(data)
try:
req = urllib2.Request(POJ.URL_LOGIN,postdata)
res = self.opener.open(POJ.URL_LOGIN,postdata).read()
if res.find(\'loginlog\')>0:
logging.info("login successful!")
return True
else:
logging.error(\'login failed\')
return False
except:
logging.error(\'login failed\')
return False
def submit(self,pid,language,src):
submit_data = dict(
problem_id = pid,
language = POJ.LANGUAGE[language.upper()],
source = src,
submit = \'Submit\',)
postdata2 = urllib.urlencode(submit_data)
try:
req2 = urllib2.Request(POJ.URL_SUBMIT,data = postdata2)
res = self.opener.open(POJ.URL_SUBMIT,postdata2).read()
logging.info(\'submit successful\')
return True
except:
logging.error(\'submit error\')
return False
def result(self,user_id):
url = POJ.URL_STATUS + urllib.urlencode({\'user_id\':user_id})
page = urllib2.urlopen(url)
soup = BeautifulSoup(page)
table = soup.findAll(\'table\',{\'class\':\'a\'}) #提取表格
pattern = re.compile(r\'>[-+: \w]*<\') #正则表达式匹配需要的信息
result = pattern.findall(str(table))
#正在评判,编译或等待
wait = [\'Running & Judging\',\'Compiling\',\'Waiting\']
for i in range(3):
if result[32][1:-1]==wait[i] or result[32][1:-1] == \'\':
logging.info(result[32])
# sleep(1)
return False
#最终结果在result序列中的位置
num = [21,24,28,32,35,37,40,43,45]
for i in range(9):
print POJ.INFO[i],\':\',result[num[i]][1:-1]
return True
if __name__==\'__main__\':
#基础logging模块配置
FORMAT = \'----%(message)s----\'
logging.basicConfig(level=logging.INFO,format = FORMAT)
if len(sys.argv) > 1: #从外部传入参数
user_id, pwd, pid, lang, src, = sys.argv[1:]
src = open(src,\'r\').read()
else: #测试
user_id = \'username\'
pwd = \'password\'
pid = 1000
lang = \'gcc\'
src = \'\'\'
#include<stdio.h>
int main()
{
int a,b;
scanf("%d%d",&a,&b);
printf("%d",a+b);
return 0;
}
\'\'\'
logging.info(\'connecting to server\')
poj = POJ(user_id,pwd)
if poj.login():
logging.info("submiting")
if poj.submit(pid,lang,src):
logging.info(\'getting result\')
status = poj.result(user_id)
while status!=True: #直到检测到结果
status = poj.result(user_id)
ZOJ
import re
import sys
import logging
from time import sleep
import urllib,urllib2,cookielib
from BeautifulSoup import BeautifulSoup
class ZOJ:
URL_HOME = \'http://acm.zju.edu.cn/onlinejudge/\'
URL_LOGIN = URL_HOME + \'login.do?\'
URL_SUBMIT = URL_HOME + \'submit.do?\'
URL_STATUS = URL_HOME + \'showRuns.do?contestId=1&\'
#结果信息
INFO =[\'RunID\',\'Submit Time\',\'Judge Status\',\'Problem ID\',
\'Language\',\'Run Time(ms)\',\'Run Memory(KB)\',\'User Name\']
#语言:为了防止出错,gcc定义为C语言,g++定义为c++,zoj没有gcc和g++选项
LANGUAGE = {
\'C\':\'1\',
\'C++\':\'2\',
\'FPC\':\'3\',
\'JAVA\':\'4\',
\'PYTHON\':\'5\',
\'PERL\':\'6\',
\'SCHEME\':\'7\',
\'PHP\':\'8\',
\'GCC\':\'1\',
\'G++\':\'2\',
}
def __init__(self, user_id, password):
self.user_id = user_id
self.password = password
cj = cookielib.LWPCookieJar()
self.opener =urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(self.opener)
def login(self):
data = dict(
handle = self.user_id,
password = self.password,
)
postdata = urllib.urlencode(data)
try:
req = urllib2.Request(ZOJ.URL_LOGIN,postdata)
res = self.opener.open(ZOJ.URL_LOGIN,postdata).read()
if res.find(self.user_id)>0:
logging.info("login successful!")
return True
else:
logging.error(\'login failed\')
return False
except:
logging.error(\'login failed\')
return False
def submit(self,pid,language,src):
submit_data = dict(
problemId = str(int(pid) - 1000),
languageId = ZOJ.LANGUAGE[language.upper()],
source = src,)
postdata2 = urllib.urlencode(submit_data)
try:
req2 = urllib2.Request(ZOJ.URL_SUBMIT,data = postdata2)
res = self.opener.open(ZOJ.URL_SUBMIT,postdata2).read()
logging.info(\'submit successful\')
return True
except:
logging.error(\'submit error\')
return False
def result(self,user_id):
url = ZOJ.URL_STATUS + urllib.urlencode({\'handle\':user_id})
page = urllib2.urlopen(url)
soup = BeautifulSoup(page)
table = soup.findAll(\'table\',{\'class\':\'list\'})
table = \'\'.join(str(table).split())
pattern = re.compile(r\'>[-+:\w]*<\')
result = pattern.findall(str(table))
wait = [\'Running\',\'Compiling\',\'Waiting\']
num = [18,20,23,27,31,34,36,40]
for i in range(3):
if result[23][1:-1]==wait[i]:
logging.info(result[23])
sleep(1)
return False
if result[23][1:-1] == \'\':
num = [18,20,24,29,33,36,38,42]
for i in range(8):
print ZOJ.INFO[i],\':\',result[num[i]][1:-1]
return True
if __name__==\'__main__\':
FORMAT = \'----%(message)s----\'
logging.basicConfig(level=logging.INFO,format = FORMAT)
if len(sys.argv) > 1:
user_id, pwd, pid, lang, src, = sys.argv[1:]
src = open(src,\'r\').read()
else:
user_id = \'username\'
pwd = \'password\'
pid = 1001
lang = \'c++\'
src = \'\'\'
#include<stdio.h>
int main()
{
int a,b;
while(scanf("%d%d",&a,&b)!=EOF)
printf("%d\\n",a+b);
return 0;
}
\'\'\'
logging.info(\'connecting to server\')
zoj = ZOJ(user_id,pwd)
if zoj.login():
logging.info(\'submiting\')
if zoj.submit(pid,lang,src):
logging.info(\'getting result\')
status = zoj.result(user_id)
while status!=True:
status = zoj.result(user_id)
先简单解释一下代码:程序用urllib模块编码数据,用urllib2模块提交,用cookielib模块保存登录信息,提交成功后用beautifulsoup模块解析网页得到表格,然后用re模块正则表达式匹配最终结果,sys模块用来从外部程序外部传入参数,整个过程用logging日志模块记录事件日志。
应该说,上面提到的模块都是经常用到的,都是应该熟练掌握的。上面只是一些简单用法,以后好要深入学习。
再说说程序的用法(以poj为例):将上面的代码保存到"poj.py",然后在终端执行这个命令:”python poj.py username password problem_id language source_code_path“ 。将上面的用户名和密码替换成你自己的用户名和密码,problem_id是你要提交的题号,如1001,语言可以选gcc,g++,java,pascal等,最后是源文件所在的目录,如果源文件在当前目录下,可以省略路径,直接写文件名。
这个程序仅仅实现了功能,用户可以根据需要自己扩展,让这个程序更易用,更实用,更符合你的要求,比如在程序中集成用户名和密码,自动识别题目号和语言,给poj.py增加执行权限然后把它所在的目录添加到环境变量或者直接把程序放到/usr/local/bin/目录下,这样的话,或许你以后用vim写完代码,然后一个命令“poj.py 1001.c”就自动交过去了,把这个程序集成到vim里面做成一键提交或许更爽!哈哈,这一切不是不可能!
说来也有趣,poj和zoj很容易就实现了,但是在弄杭电时,却一直没成功,貌似杭电有防止网络爬虫的机制,杭电自己在其他oj上刷题,却不让别人在自己oj上刷。或许是我学艺不精,还要好好研究。网上说需要增加header,也就是模拟浏览器才能登录,但是登录以后提交却一直没提交成功,也不知道为什么,哪位大神知道帮忙解决一下?小弟感激不尽!
上面只是一个demo,真正用到webdiy里面的话还要进行扩充,比如增加数据库的支持,还要抓取题目和编译错误信息。当提交量大的时候如何进行调度,网络不给力怎么办,出现错误如何处理等,这都是需要考虑的问题。上面的程序只是一个后台程序,如果做一个webdiy的话还要有前台的展示页面和后台的管理页面,正在学习django模块,希望能用这个框架做。