hack狂魔养成记

r_64 posted @ 2016年4月28日 02:16 in 未分类 with tags python , 15910 阅读

因为最近蛋疼,所以想叉几个人。。。

每次手动点开一个人的提交记录然后把程序拷下来拍拍拍就觉得很烦。。因为基本上都是对的,而不是数据水。。只有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就好。很简单就把代码扒下来,用HTMLParserunescape方法就可以得到源码了。

具体说就是找<code>***</code>,把它们中间的所有东西都抓出来,就是代码咯。

get_source.py。函数GetSourceCode(s_no)返回编号为s_no的源程序。


第二步:拍

大概要用到python的os库和shutilcommand等库的几个函数。

因为程序大部分都是对的,所以我就没写时空限制和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等。

然后新建文件夹gengen里面有常用的数据生成器,名字就是$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成功之后你需要删除一些程序,比如说./$pid/$sid.cpp。这样的话./$pid/list.txt中的$sid也要手动被删。

最后晒一下我的工作目录,作为一个例子(里面的省略号是我改动了的,有个大概意思就好;不要在意'~'结尾的文件名,它们是草稿):

.:
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器?

 


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter