声明:本文以学习为目的,请不要影响他人正常判题
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之旅吧!