教你用python写:HDU刷题神器
声明:本文以学习为目的,请不要影响他人正常判题
HDU刷题神器,早已被前辈们做出来了,不过没有见过用python写的。大一的时候见识了学长写这个,当时还是一脸懵逼,只知道这玩意儿好屌…。时隔一年,决定自己实现这个功能。

96名,没有再继续刷(,,,已经被管理员发现啦)
首先对辛苦刷题的acmer和hdu的管理员道歉,各位,抱歉。
介绍整体思路:
- 整体用多线程:线程执行从爬代码到提交的全部过程
- 分层次:对搜索引擎搜索的结果,进行划分,分层爬取
局部思路:
- 爬取搜索引擎得到的与题目相关的url,得到url_list
- 爬取url_list中的url,扒到代码就提交
- 检查提交结果,WA之后继续爬取url_list中的代码
- 循环,直到列表为空或者AC
相关模块:
- threadpool线程池,分配线程任务,多线程并发提交代码
- 用requests模块发送请求
- 正则爬取url和代码
- Sqlite存放AC代码(打表啊,再申请个账号从数据库中提交代码100%AC)
1)采用线程池实现多线程,注意控制最大并发数量
搜索引擎使用CSDN的搜索,因为我们爬取的代码全都来自CSDN的博客,可以看一下其他论坛,博客的代码:

(右键,在新标签页中打开查看高清图片)


(右键,在新标签页中打开查看高清图片)

哦,这实在太不友好了,而CSDN博客的代码就好很多了(尽管很友好了,class和name有些先后顺序不一样,也会添乱)

所以,我们决定扒CSDN博客的代码。
搜索引擎的选择,CSDN(部分搜索结果是百度提供的)

其实,第一想到的是百度的,然而。。。

加密了,增大了我们的工作量,所以,就直接用CSDN的(也有百度的结果)
在CSDN搜索结果的最下方,我们可以看到上图中有14W结果(好唬人啊),其实事情是这样的:


这是一个搜索hdu 1000的url,我们注意到用的get()方法传数据,发现只有p=?,试一下就知道,这个是页码。如果页码改为200呢?

100?

开玩笑啊,14W结果呢?最后我们得出结论:搜索结果只有页,而且越往后,得到我们想要代码的可能性就越小,所以我只爬到20页就结束程序
关于线程池的部分,在python-线程池里说明
# coding :utf-8
# @Time : 2017/8/7 11:24
# @Author : Yong-life
# @File : crawling_hdu.py
import requests
import re
from headers_cookies import *
import threadpool
from searching_code import code_url_list
import threading
from searching_code import submit_code_from_url
import time
'''我要AK HDU'''
def search_pid():
problem_list = []
'''共52页题目'''
page_number = 2
for i in range(page_number):
'''请求url'''
url = 'http://acm.hdu.edu.cn/listproblem.php?vol=' + str(i)
response = requests.get(url, headers=get_headers(), cookies=get_cookie_from_chrome())
'''抓取题目信息'''
patternPidList = r'><script language="javascript">([\s\S]*?);</script>'
problems = re.search(patternPidList, response.text).group(1).split(';')
for problem in problems:
pid = problem[4:8]
status = problem[9]
problem_list.append((int(pid), int(status)))
return problem_list
def start_crawling():
pid_list = []
'''最大并发数量,超过10就很影响别人'''
THREAD_NUMBER = 2
'''从搜索到的第crawl_level_begin页继续开始扒代码'''
crawl_level_begin = 1
'''分段式扒代码'''
crawl_level = 4
END_MARK = True
while END_MARK:
print("正在爬取题目信息……")
for problem in search_pid():
pid, status = problem
# if pid < 3300:
# continue
if status != 5:
'''多参数构造'''
pid_list.append(([pid, crawl_level_begin, crawl_level], None)) # 必须封装为元组,否则会给参数再封装一层列表,也可以改目标函数
if len(pid_list) == THREAD_NUMBER:
'''定义线程池大小'''
task_pool = threadpool.ThreadPool(THREAD_NUMBER)
'''任务列表'''
request_task = []
'''构造任务列表'''
request_task = threadpool.makeRequests(submit_code_from_url, pid_list)
'''将每个任务放到线程池中,等待线程池中线程各自读取任务'''
[task_pool.putRequest(req) for req in request_task]
'''等待所有任务处理完成,则返回,如果没有处理完,则一直阻塞'''
task_pool.wait()
pid_list = []
'''判断是否全对,全对结束'''
END_MARK = False
for status in search_pid()[1]:
if status != 5:
END_MARK = True
'''增加搜索范围'''
crawl_level_begin = crawl_level
crawl_level += 4
break
'''CSDN搜索最搜索到20页数据,而且部分是百度提供的结果'''
if crawl_level > 20:
break
if __name__ == '__main__':
start_crawling()
2)线程开始跑了:分布式思想,分块,分层,完成AK任务
- 爬取crawl_level_begin-crawl_level页搜索结果的url
- 按照url_list爬取代码
- 提交代码
# coding :utf-8
# @Time : 2017/8/7 15:06
# @Author : Yong-life
# @File : searching_code.py
from urllib import request
import urllib
from headers_cookies import get_headers
import re
import sqlite3
from sqlite_hdu import Sql
from submit_codes import *
import threading
from submit_codes import SubmitCode
import sys
def submit_code_from_url(pid, crawl_level_begin, crawl_level):
'''爬取url_list,提交代码'''
url_list = code_url_list(str(pid), crawl_level_begin, crawl_level)
for url in url_list:
'''仅爬取博客链接'''
if url[7:11] != 'blog': # 删选url
continue
code = crawling_code(pid, url)
if code == '':
'''未查找到代码'''
continue
submit = SubmitCode(pid, code.encode("utf-8"))
if submit.submit_manager():
'''AC,保存代码'''
sql_save_code = Sql()
if sql_save_code.query_pid(pid) is None:
sql_save_code.insert_msg(pid, 5, code)
else:
sql_save_code.update_problem_code(pid, code)
sql_save_code.sql_close()
return
print("爬取代码完毕,任务结束: " + str(pid) + "提交尚未成功!")
return
def code_url_list(problem_msg, crawl_level_begin, crawl_level): # 题号和其他信息
'''爬取题目链接'''
url_list = []
'''页码,根据爬虫等级,扩大搜索范围'''
page_number = crawl_level_begin
'''最大页码'''
MAX_PAGE = crawl_level
while page_number < MAX_PAGE:
'''发送url请求'''
url = 'http://so.csdn.net/so/search/s.do?p=' + str(page_number) + '&q=' + 'hdu' + request.quote(problem_msg)
req = request.Request(url, headers=get_headers())
try:
respongse = request.urlopen(req).read().decode('utf-8')
except urllib.error.HTTPError:
continue
'''爬取url链接'''
pattern_problem_url = '<dt>[\S\s]*?<a href="(.*?)"'
result_list = re.findall(pattern_problem_url, respongse)
url_list.extend(result_list)
print(problem_msg + ': ' + str(page_number) + '页已搜索完毕!')
page_number += 1
print('题目url列表爬取完毕!!!')
return url_list
def crawling_code(pid, code_url):
'''爬代码'''
req = request.Request(code_url, headers=get_headers())
response = ''
try:
response = request.urlopen(req).read().decode('utf-8')
except urllib.error.HTTPError as e:
print(e)
'''查找C,C++代码'''
pattern_code_cpp = 'class="cpp">([\s\S]*?)</pre>'
code = re.search(pattern_code_cpp, response)
if code is not None:
print(str(pid) + 'cpp, 已找到!')
'''对代码中html元素进行处理'''
code = ' + code.group(1)
code = translate_code(code)
return code
'''查找JAVA代码'''
pattern_code_java = 'class="java">([\s\S]*?)</pre>'
code = re.search(pattern_code_java, response)
if code is not None:
print(str(pid) + 'java, 已找到!')
code = ' + code.group(1)
code = translate_code(code)
return code
return ''
3)对代码中的html元素处理
Compilation Error次数多了就知道什么元素没处理了
# coding :utf-8
# @Time : 2017/8/7 19:08
# @Author : Yong-life
# @File : translate_code.py
def translate_code(code):
'''转化代码中的html元素'''
code = code.replace('<', '<')
code = code.replace('>', '>')
code = code.replace('"', '"')
code = code.replace('&', '&')
code = code.replace('+', '+')
code = code.replace(''', '\'')
code = code.replace(' ', ' ')
code = code.replace(' ', ' ')
'''替换'''
code = code.replace('</pre>', ' ')
code = code.replace('</div>', ' ')
code = code.replace('<pre>', ' ')
code = code.replace('<div>', ' ')
code = code.replace('</span>', ' ')
return code
4)根据不同语言,选择不同编译器提交代码,并检查代码结果
我们爬取了c,c++,java的代码,提交代码时,要注意选择编译器,我们需要通过提交不同的代码查看请求参数的不同。
这里推荐一款抓包软件:fiddler,小型,实用
关于fiddler的说明:使用fiddler进行抓包测试
测试后,发现:

c:3
c++: 2
G++: 0
JAVA: 5
OK!那么我们在post提交时,修改language参数就可以实现使用不同的编译器进行提交了。提交后,递归检查提交结果。
检查结果时:注意author必须是账户名,用昵称是搜不到

# coding :utf-8
# @Time : 2017/8/7 15:00
# @Author : Yong-life
# @File : submit_codes.py
import requests
from sqlite_hdu import Sql
from headers_cookies import *
from translate_code import translate_code
import re
import time
class SubmitCode():
'''题目号,代码'''
def __init__(self, pid, code):
self.cu = Sql()
self.pid = pid
self.code = code
def submit_manager(self):
print("提交代码,休息3秒: " + str(self.pid))
self.submit(self.pid, self.code)
'''提交代码,休息3秒'''
time.sleep(3)
if self.query_result(self.pid):
print("题号: " + str(self.pid) + "已AC")
self.cu.sql_close()
return True
else:
print("题号: " + str(self.pid) + "WA ,正在继续提交!")
self.cu.sql_close()
return False
'''提交代码'''
def submit(self, pid, code):
url = 'http://acm.hdu.edu.cn/submit.php?action=submit'
try:
data = {
',
'problemid': str(pid),
'language': code[0],
'usercode': code[1:]
}
except IndexError:
print("empty code")
return
response = requests.post(url, data, headers=get_headers(), cookies=get_cookie_from_chrome())
'''检查提交结果'''
def query_result(self, pid):
USER_NAME = self.get_user_name()
'''请求url'''
url = 'http://acm.hdu.edu.cn/status.php?first=&pid=' + str(pid) + '&user=' + USER_NAME + '&lang=0&status=0'
response = requests.get(url, headers=get_headers(), cookies=get_cookie_from_chrome())
'''检查结果'''
pattern_query = r'<td><font color=red>(.*?)</font>'
query_result = re.findall(pattern_query, response.text)
if len(query_result) > 0:
'''AC'''
return True
else:
'''判断代码提交代码状态'''
'''是否在判题中'''
if response.text.find("Queuing") != -1 or response.text.find("Running") != -1 or response.text.find(
"Compiling") != -1:
'''等待判题中,暂不提交'''
print('等待判题')
time.sleep(1)
return self.query_result(pid)
else:
'''代码WA'''
return False
'''爬取账号名,账号名和昵称,只能用账号名查找结果'''
def get_user_name(self):
url = 'http://acm.hdu.edu.cn/'
response = requests.get(url, headers=get_headers(), cookies=get_cookie_from_chrome()).text
pattern_user_name = r'width:150px"><a href="/userstatus.php\?user=(.*?)"'
user_name = re.search(pattern_user_name, response).group(1)
return user_name
5)将AC代码存入数据库,代表,以备100%AK
注意:sql查找语句,传值必须是元组的形式,即使只有一个值,要加‘,’
# coding :utf-8
# @Time : 2017/8/7 15:42
# @Author : Yong-life
# @File : sqlite_hdu.py
import sqlite3
class Sql():
def __init__(self):
file_path = 'E:\python_workspace\crawling_hdu\sql_databases\hdu.db'
self.con = sqlite3.connect(file_path, check_same_thread=False) # 支持多线程访问
self.cu = self.con.cursor()
# self.cu.execute('DROP TABLE IF EXISTS problem')
self.cu.execute("CREATE TABLE IF NOT EXISTS problem (pid INTEGER PRIMARY KEY , status INT, code TEXT)")
def insert_msg(self, pid, status=0, code=''):
'''插入信息'''
value = (pid, status, code)
self.cu.execute("INSERT INTO problem(pid, status, code) VALUES (?,?,?)", value)
self.con.commit()
def query_code(self, pid):
'''查找代码'''
self.cu.execute("SELECT code FROM problem WHERE pid=?", (pid,)) # why
return self.cu.fetchone()[0]
def query_pid(self, pid):
'''检查代码是否已存库'''
self.cu.execute("SELECT pid FROM problem WHERE pid=?", (pid,)) # why
return self.cu.fetchone()
def delete_problem_msg(self, pid):
'''删除'''
self.cu.execute("DELETE FROM problem WHERE pid=?", (pid,)) # why
self.con.commit()
def update_problem_code(self, pid, code):
'''更新代码'''
self.cu.execute("UPDATE problem SET code=? WHERE pid=?", (code, pid))
self.con.commit()
def sql_close(self):
self.cu.close()
self.con.close()
6)cookie与header的获取:
cookie可以从google的chrome浏览器获取,路径在:“C:\Users\Garbos\AppData\Local\Google\Chrome\User Data\Default\Cookies”,对应改一下用户名就好了
# coding :utf-8
# @Time : 2017/7/30 16:42
# @Author : Jingxiao Fu
# @File : headers_cookies.py
import random
import os
import subprocess
import sqlite3
import win32crypt
import sys
header_str = '''Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50
Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)'''
def get_headers():
header = header_str.split('\n')
header_length = len(header)
headers = {'user-agent': header[random.randint(0, header_length - 1)]}
return headers
def get_cookie_use_hand():
'''手动复制cookie'''
cookie = "Cookie:exesubmitlang=0; PHPSESSID=uqfje2cajo7j3kicrn8d2fol25"
cookie = cookie.replace('Cookie:', '')
cookie = cookie.replace(' ', '')
cookies = cookie.split(';')
for i in range(len(cookies)):
cookies[i] = cookies[i].replace('=', ':', 1)
cookies_dict = {}
for header in cookies:
L = header.split(':', 1)
cookies_dict[L[0]] = L[1]
return cookies_dict
'''从chrome浏览器获取cookie'''
def get_cookie_from_chrome():
host_url = 'acm.hdu.edu.cn'
cookie_file_path = r"C:\Users\Garbos\AppData\Local\Google\Chrome\User Data\Default\Cookies"
sql_query = "select host_key, name, encrypted_value ,value from cookies WHERE host_key='%s'" % host_url
with sqlite3.connect(cookie_file_path) as con:
cu = con.cursor()
cu.execute(sql_query)
cookies_sql = {name: win32crypt.CryptUnprotectData(encrypted_value)[1].decode() for
host_key, name, encrypted_value, value in cu.execute(sql_query).fetchall()}
return cookies_sql
开始你的AK之旅吧!
教你用python写:HDU刷题神器的更多相关文章
- 手把手教你用C++ 写ACM自动刷题神器(冲入HDU首页)
转载注明原地址:http://blog.csdn.net/nk_test/article/details/49497017 少年,作为苦练ACM,通宵刷题的你 是不是想着有一天能够荣登各大OJ榜首,俯 ...
- python在leecode刷题-第一题和第七题
class Solution(object): def twoSum(self, nums, target): """ :type nums: List[int] num ...
- python(9)- python基础知识刷题
1. 执行 Python 脚本的两种方式 交互方式:命令行 Windows操作系统下,快捷键cmd,输入“python”启动交互式python解释器. 文件方式:python文件 2. 简述位.字 ...
- hdu 刷题记录
1007 最近点对问题,采用分治法策略搞定 #include<iostream> #include<cmath> #include<algorithm> using ...
- hdu刷题2
hdu1021 给n,看费波纳列数能否被3整除 算是找规律吧,以后碰到这种题就打打表找找规律吧 #include <stdio.h> int main(void) { int n; whi ...
- 500行代码,教你用python写个微信飞机大战
这几天在重温微信小游戏的飞机大战,玩着玩着就在思考人生了,这飞机大战怎么就可以做的那么好,操作简单,简单上手. 帮助蹲厕族.YP族.饭圈女孩在无聊之余可以有一样东西让他们振作起来!让他们的左手 / 右 ...
- (python)leetcode刷题笔记 02 Add Two Numbers
2. Add Two Numbers You are given two non-empty linked lists representing two non-negative integers. ...
- (python)leetcode刷题笔记 01 TWO SUM
1. Two Sum Given an array of integers, return indices of the two numbers such that they add up to a ...
- hdu刷题1
1002 大数加法 #include<iostream> #include<cstring> using namespace std; int main() { ],b[]; ...
随机推荐
- [leetcode-495-Teemo Attacking]
In LLP world, there is a hero called Teemo and his attacking can make his enemy Ashe be in poisoned ...
- jQuery(二) jQuery对Ajax的使用
学习使我快乐!嘿 --WH 一.jQuery使用Ajax 想要了解jQuery如何使用Ajax,并且体会到它所带来的方便性,那么就得了解原始的Ajax是如何编写的,是怎样的繁琐,然后和Jquery的代 ...
- 【操作教程】SequoiaDB分布式存储教程
1.各模式适用场景介绍 由于SequoiaDB对比其他的NoSQL有更多的方式将数据分布到多台服务器上,所以下面笔者为阅读者一一介绍每种分布式方式适合于哪种场景. 1.1 Hash 方式分布数据 在H ...
- ecshop中smarty比较操作符(eq,ne,neq)含义
eq相等, ne.neq不相等, gt大于, lt小于, gte.ge大于等于, lte.le 小于等于, not非, mod求模. is [not] div by是否能被某数整除, is [not ...
- Web开发资料
慢慢更新 1. Quackit 墙裂推荐!提供了一系列教程,bootstrap的模板也很好用. 2. Bootstrap 4 Cheat Sheet 好用,比官网更加一目了染.  3.Chart. ...
- MyBatis源码解析【4】反射和动态代理
通过之前的介绍,我们了解了几个组件的生命周期. 它也是我们重要装备之一. 今天我们需要搞一件更加强的装备,叫做反射和动态代理. 如果没有这件装备的话,显然后面的源码boss是打不动的. 顺便说一下,下 ...
- Jsp注册页面身份证验证
<!--身份证验证 --><script type="text/javascript">function isCardNo(Idcardnumber) { ...
- hdu_5964:平行四边形
打重现赛时,一点思路也没有,然后又看到这题AC数那么少,就直接放弃了.今天重新看了看,借鉴了下别人的,发现此题应该算是一道可解题. 看上去,这题的ans是同时有两个点作为自变量的函数(然而n^2复杂度 ...
- 各种demo:css实现三角形,css大小梯形,svg使用
各种demo: 1.css实现正方形 思路:width为0:height为0:使用boder-width为正方形的边长的一半,不占任何字节:border-style为固体:border-color为正 ...
- Spring源码情操陶冶-AbstractApplicationContext#initMessageSource
承接前文Spring源码情操陶冶-AbstractApplicationContext#registerBeanPostProcessors 约定web.xml配置的contextClass为默认值X ...