hack狂魔养成记
因为最近蛋疼,所以想叉几个人。。。
每次手动点开一个人的提交记录然后把程序拷下来拍拍拍就觉得很烦。。因为基本上都是对的,而不是数据水。。只有XLightGod能给我惊喜
于是决定。。写一个程序抓取uoj上某题的所有ac程序,再写一个程序把这些程序一起拍。
嗯。。我想说的就是。。python太棒了,你信吗我的爬虫和对拍器加起来不到4k,这还是我的代码风格,动不动就加空格。
不接触还不知道,python这么适合写工程。
代码准备放到github上。。我还不会用github呢
以及我的python版本是2.7.6。
第一步:抓程序。
学习python爬虫看这里
SGMLParser请看这里
如果要扒代码的话,这里可以把HTML的代码变成文本(比如说把'<'这种东西变成小于号,把' '变成空格)
于是一个不会用数组的家伙开始写爬虫辣
首先对于一个给定的题目$pid,访问http://uoj.ac/submissions?problem_id=$pid&min_score=100&page=$pg就可以抓到第$pg页的AC程序。如果想抓到所有AC程序的话,我们不妨枚举$pg。因为$pg足够大的时候总是显示最后一页,所以从小到大枚举$pg,抓到两次相同的数据就证明抓完了。手动高亮哦
怎么抓呢,就是把源码扒下来,每次找不在表头(<thead>)内的表格,每一行是<tr>,每一格是<td>,抓第一格的<a href='***'>中那个***里面就有$submission_id;第7格是语言(为了编译这个程序我们显然需要得到语言)。每一行如果不在<thead>内,就把$submission_id和$language搞出来放入一个list中就好。
代码get_submissions.py中的函数GetList(prob)负责获取一个题目prob的所有submission的信息。返回一个列表,每个元素是二元组(submission_id,language) 。
然后对于一个给定的$submission_id我们需要搞到它的源码。访问http://uoj.ac/submission/$submission_id就好。很简单就把代码扒下来,用HTMLParser的unescape方法就可以得到源码了。
具体说就是找<code>***</code>,把它们中间的所有东西都抓出来,就是代码咯。
见get_source.py。函数GetSourceCode(s_no)返回编号为s_no的源程序。
第二步:拍
大概要用到python的os库和shutil、command等库的几个函数。
因为程序大部分都是对的,所以我就没写时空限制和Dangerous Syscall的限制。
首先把源程序都保存下来。然后一个一个编译。然后搞到数据生成器(这个只能自己写),就可以拍了。
为了减少爬虫的使用次数,我把所有AC程序的编号存在一个txt文件里面,这样就不用每次拍都爬一遍uoj了。
中间各种问题不会。。还好一问谷歌就会回答“python里面有函数是专门干这个的呀”。python真叼。。比如用command.getstatusoutput(cmd)函数获取返回结果,返回一个二元组(status,output)。status就是C++里面的“status=system(cmd);”,output就是程序在终端内的输出。这个函数与diff命令被我用来比较两个程序的输出。
废话少说,放码过来。
from urllib2 import * from sgmllib import SGMLParser tot_submissions = [] last_submissions = [] language = [] class Spider_For_Submission_List(SGMLParser) : in_thead = 0 cnt_item = 0 in_a = 0 submissions = [] cur_no = 0 def start_thead(self, attrs) : self.in_thead = 1 def end_thead(self) : self.in_thead = 0 def start_tr(self, attrs) : self.cnt_item = 0 def start_td(self, attrs) : self.cnt_item = self.cnt_item + 1 def start_a(self, attrs) : self.in_a = 1 if self.in_thead == 0 : if self.cnt_item == 1 : href = [v for k, v in attrs if k == 'href'] if href : _href = href[0] submission_no = _href[len('/submission/'):] self.cur_no = submission_no def end_a(self) : self.in_a = 0 def handle_data(self, text) : if self.in_thead == 0 : if self.cnt_item == 7 : self.submissions.append([self.cur_no, text]) sfsl = Spider_For_Submission_List() def GetList_(prob, page) : global last_submissions, tot_submissions print "Fetching Problem " + str(prob) + ", Page " + str(page) content = urlopen("http://uoj.ac/submissions?problem_id=" + str(prob) + "&min_score=100&page=" + str(page)).read() sfsl.submissions = [] sfsl.feed(content) if last_submissions == sfsl.submissions : return 0 else : tot_submissions.extend(sfsl.submissions) last_submissions = sfsl.submissions return 1 def GetList(prob) : i = 1 old_content = "2333" content = "" while GetList_(prob, i) : i = i + 1 return tot_submissions
GetList(prob)返回[['1','C++'],['2','C++11'],['5','Pascal']]这样的列表。
from urllib2 import * from sgmllib import SGMLParser import HTMLParser class Spider_For_Source_Code(SGMLParser) : in_code = 0 source_code = "" def start_code(self, attrs) : self.in_code = 1 def end_code(self) : self.in_code = 0 def handle_data(self, text) : if (self.in_code) : self.source_code = self.source_code + HTMLParser.HTMLParser().unescape(text) sfsc = Spider_For_Source_Code() def GetSourceCode(s_no) : print "Fetching sid = " + str(s_no) content = urlopen("http://uoj.ac/submission/" + str(s_no)).read() sfsc.source_code = "" sfsc.feed(content) return sfsc.source_code
GetSourceCode返回一个字符串表示源码。
from get_source import * from get_submissions import * from shutil import * from commands import * import os ''' pid = 75 tot = GetList(pid) ''' tot = [] def Compile(src) : fn = src[0] lang = src[1] cmd = "" if lang == "C++" : cmd = "g++ " + fn + ".cpp -o " + fn + " -lm -O2 -DONLINE_JUDGE" elif lang == "C++11" : cmd = "g++ " + fn + ".cpp -o " + fn + " -lm -O2 -DONLINE_JUDGE -std=c++11" elif lang == "C" : cmd = "gcc " + fn + ".cpp -o " + fn + " -lm -O2 -DONLINE_JUDGE" elif lang == "Pascal" : cmd = "fpc " + fn + ".cpp -O2" else : return 1 os.system(cmd) def DownloadAllSources(pid) : if os.path.isdir(str(pid)) == 0: os.mkdir(str(pid)) os.chdir(str(pid)) global tot tot = GetList(pid) flist = open("list.txt", "w") for src in tot : fout = open(src[0] + ".cpp", "w") fout.write(GetSourceCode(int(src[0]))) fout.close() flist.write(src[0] + " " + src[1] + "\n") Compile(src) flist.close() def CompileGenerator(pid) : gen = '../gen/' + str(pid) + '.cpp' dst = 'gen.cpp' print gen if os.path.isfile(gen) == 0 : print "No Generator !" return 1 print "Compiling generator ..." copyfile(gen, dst) cmd = "g++ -o gen gen.cpp" os.system(cmd) def RunOnce() : hacked = 0 os.system("./gen > in") for src in tot : print "Running " + src[0] os.system("./" + src[0] + " < in > " + src[0] + ".out") base = tot[0][0] for src in tot : (status, output) = getstatusoutput("diff -w " + base + ".out " + src[0] + ".out") if status != 0 : hacked = 1 print src[0] return hacked def ReadTot() : f = open("list.txt") global tot tot = [] content = f.readlines() for line in content : a = line.split(' ') a[1] = a[1][:-1] # Fuck off '\n' tot.append(a) def Pai(pid) : os.chdir(str(pid)) CompileGenerator(pid) ReadTot() a = 0 while a == 0 : a = RunOnce() pid = 103 #DownloadAllSources(pid) Pai(pid)
其实download_all.py有两个用途。用DownloadAllSources(pid)可以把所有AC程序弄到本地,Pai(pid)就是对拍了。
使用说明
首先搞一个文件夹做为当前目录。
然后把三个python文件放进来。即:./download_all.py等。
然后新建文件夹gen。gen里面有常用的数据生成器,名字就是$pid.cpp。例如:./gen/2.cpp。
然后运行python download_all.py。此时需要保证以下三点之一:
- 主程序中只有DownloadAllSources(pid)。此时会下载题目pid的所有AC源码。注意:后缀名永远是cpp,不管是什么语言。
- 主程序中只有Pai(pid),且AC源码已经下载完了。这时候就会拍拍拍。
-
主程序中是先DownloadAllSources(pid)再Pai(pid)。那么必须删除Pai(pid)的第一句话(否则你会进入一个叫./
$pid/ $pid的不存在的文件夹)。
当拍死了的时候,python程序会终止,显示一些数表示wa了的$submission_id。因为我是默认编号最小(最大?)的程序为标程,所以如果显示了太多数就要考虑是不是这些数的补集wa掉了。
本地hack成功之后最好在uoj上自定义测试一下。因为两边系统可能不一样(不过几率好像很小)
hack成功之后你需要删除一些程序,比如说./
最后晒一下我的工作目录,作为一个例子(里面的省略号是我改动了的,有个大概意思就好;不要在意'~'结尾的文件名,它们是草稿):
.: 103 download_all.py get_source.py get_submissions.py~ 37 download_all.py~ get_source.py~ get_submissions.pyc a.html~ gen get_source.pyc create.py~ get.py~ get_submissions.py ./103: 15044 16153.cpp 16878.cpp 25018.cpp 48549.out 58938.out 64723 15044.cpp 16153.out 16878.out 25018.out 50397 58945 64723.cpp 15044.out 16157 16906 25085 50397.cpp 58945.cpp 64723.out ... ./37: 10680 20230.cpp 3732.out 4038 41559.out 5635 66737.cpp 10680.cpp 20230.out 3734 4038.cpp 43331 5635.cpp 66737.out 10680.out 20233 3734.cpp 4038.out 43331.cpp 5635.out 6.out ... ./gen: 103.cpp 125.cpp ...
不完善的地方
不资瓷编译java和python的程序。。不资瓷spj。。
不过这个小工程应该不会更新了,所以谁到时候fork一下,改版成一个更友好的hack器?
2016年4月28日 13:59
orzzzzzzzzz
2016年4月28日 13:59
orzzzzzzzzzzzz