杭州電子科技大學的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模塊,希望能用這個框架做。