聲明:本文以學習為目的,請不要影響他人正常判題
HDU刷題神器,早已被前輩們做出來了,不過沒有見過用python寫的。大一的時候見識了學長寫這個,當時還是一臉懵逼,只知道這玩意兒好屌…。時隔一年,決定自己實現這個功能。
刷到第13名,AC率不高,因為,搜索引擎搜到的結果,往后就很難找到正確的代碼了
首先對辛苦刷題的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結果呢?最后我們得出結論:搜索結果只有76頁,而且越往后,得到我們想要代碼的可能性就越小,所以我只爬到20頁就結束程序
關於線程池的部分,在大牛博客說的很清楚,通過threadpool模塊的源碼,可以理解的很清楚
注意線程池添加任務,給任務傳遞多參數,需要查看源碼,理解參數傳遞的過程,解決辦法
1 # coding :utf-8 2 # @Time : 2017/8/7 11:24 3 # @Author : Yong-life 4 # @File : crawling_hdu.py 5 6 import requests 7 import re 8 from headers_cookies import * 9 import threadpool 10 from searching_code import submit_code_from_url 11 12 ''' 13 我要AK HDU 14 15 需要安裝threadpool,requests,pywin32模塊 16 先打開chrome瀏覽器登錄hdu(cookies是從chrome拿的,剛登陸后可能因為cookie的問題程序拋出ValueError的異常,多啟動幾次就好了) 17 ''' 18 19 20 def search_pid(): 21 problem_list = [] 22 '''共52頁題目''' 23 page_number = 52 + 1 24 for i in range(page_number): 25 '''請求url''' 26 url = 'http://acm.hdu.edu.cn/listproblem.php?vol=' + str(i) 27 response = requests.get(url, headers=get_headers(), cookies=get_cookie_from_chrome()) 28 '''抓取題目信息''' 29 patternPidList = r'><script language="javascript">([\s\S]*?);</script>' 30 problems = re.search(patternPidList, response.text).group(1).split(';') 31 for problem in problems: 32 try: 33 pid = int(problem[4:8]) 34 status = int(problem[9]) 35 problem_list.append((pid, status)) 36 except ValueError: 37 '''ValueError''' 38 print(type(pid), pid, type(status), status) 39 40 return problem_list 41 42 43 def start_crawling(): 44 pid_list = [] 45 '''最大並發數量,超過10就很影響別人,相當於同時有5個人在提交代碼,提交一次判斷完成后才會繼續提交其他代碼''' 46 THREAD_NUMBER = 5 47 '''從搜索到的第crawl_level_begin頁繼續開始扒代碼''' 48 crawl_level_begin = 1 49 '''分段式扒代碼''' 50 crawl_level = 4 51 END_MARK = True 52 while END_MARK: 53 print("正在爬取題目信息……") 54 for problem in search_pid(): 55 pid, status = problem 56 if status != 5: 57 '''多參數構造''' 58 pid_list.append(([pid, crawl_level_begin, crawl_level], None)) # 必須封裝為元組,否則會給參數再封裝一層列表,也可以改目標函數 59 if len(pid_list) == THREAD_NUMBER: 60 '''定義線程池大小''' 61 task_pool = threadpool.ThreadPool(THREAD_NUMBER) 62 '''任務列表''' 63 request_task = [] 64 '''構造任務列表''' 65 request_task = threadpool.makeRequests(submit_code_from_url, pid_list) 66 '''將每個任務放到線程池中,等待線程池中線程各自讀取任務''' 67 [task_pool.putRequest(req) for req in request_task] 68 '''等待所有任務處理完成,則返回,如果沒有處理完,則一直阻塞''' 69 task_pool.wait() 70 71 pid_list = [] 72 '''判斷是否全對,全對結束''' 73 END_MARK = False 74 print('正在搜索AC情況,,,') 75 for status in search_pid()[1]: 76 if status != 5: 77 END_MARK = True 78 print('未AK...') 79 '''增加搜索范圍''' 80 crawl_level_begin = crawl_level 81 crawl_level += 4 82 print('增大搜索范圍繼續搜索...') 83 print('當前搜索范圍:', crawl_level_begin, crawl_level) 84 break 85 86 '''CSDN搜索最搜索到30頁數據,而且部分是百度提供的結果''' 87 if crawl_level > 30: 88 break 89 90 print('AK!') 91 92 93 if __name__ == '__main__': 94 start_crawling()
2)線程開始跑了:分布式思想,分塊,分層,完成AK任務
- 爬取crawl_level_begin-crawl_level頁搜索結果的url
- 按照url_list爬取代碼
- 提交代碼
每輪爬完6000+題后,判斷是否AK,AK則結束程序,否則增加crawl_level繼續查找代碼提交
經過剪枝優化,代碼更快了
1 # coding :utf-8 2 # @Time : 2017/8/7 15:06 3 # @Author : Yong-life 4 # @File : searching_code.py 5 6 from urllib import request 7 import urllib 8 from headers_cookies import get_headers 9 import re 10 import sqlite3 11 from sqlite_hdu import Sql 12 from submit_codes import * 13 import threading 14 from submit_codes import SubmitCode 15 import sys 16 17 18 def submit_code_from_url(pid, crawl_level_begin, crawl_level): 19 '''爬取url_list,提交代碼''' 20 url_list = code_url_list(str(pid), crawl_level_begin, crawl_level) 21 for url in url_list: 22 '''僅爬取博客鏈接''' 23 if url[7:11] != 'blog': # 刪選url 24 continue 25 code = crawling_code(pid, url) 26 if code == '': 27 '''未查找到代碼''' 28 continue 29 submit = SubmitCode(pid, code) 30 if submit.submit_manager(): 31 '''AC,保存代碼''' 32 sql_save_code = Sql() 33 if sql_save_code.query_pid(pid) is None: 34 sql_save_code.insert_msg(pid, 5, code) 35 else: 36 sql_save_code.update_problem_code(pid, code) 37 sql_save_code.sql_close() 38 return 39 40 print("爬取代碼完畢,任務結束: " + str(pid) + "提交尚未成功!") 41 return 42 43 44 def code_url_list(problem_msg, crawl_level_begin, crawl_level): # 題號和其他信息 45 '''爬取題目鏈接''' 46 url_list = [] 47 '''頁碼,根據爬蟲等級,擴大搜索范圍''' 48 page_number = crawl_level_begin 49 '''最大頁碼''' 50 MAX_PAGE = crawl_level 51 while page_number < MAX_PAGE: 52 '''發送url請求''' 53 url = 'http://so.csdn.net/so/search/s.do?p=' + str(page_number) + '&q=' + 'hdu' + request.quote(problem_msg) 54 req = request.Request(url, headers=get_headers()) 55 try: 56 respongse = request.urlopen(req).read().decode('utf-8') 57 except urllib.error.HTTPError: 58 continue 59 '''爬取url鏈接''' 60 pattern_problem_url = '<dt>[\s]*?<a href="(.*?)"? target="_blank" ([\s\S]*?)</a>' 61 '''url的title中沒有發現題目信息,刪去''' 62 result_list = re.findall(pattern_problem_url, respongse) 63 for i in result_list: 64 if i[1].find(problem_msg) == -1: 65 result_list.remove(i) 66 result = result_list[:] 67 result_list = [] 68 result_list.extend(url[0] for url in result) 69 url_list.extend(result_list) 70 71 print(problem_msg + ': ' + str(page_number) + '頁已搜索完畢!') 72 page_number += 1 73 74 print('題目url列表爬取完畢!!!') 75 return url_list 76 77 78 def crawling_code(pid, code_url): 79 '''爬代碼''' 80 req = request.Request(code_url, headers=get_headers()) 81 response = '' 82 try: 83 response = request.urlopen(req).read().decode('utf-8') 84 except urllib.error.HTTPError as e: 85 print(e) 86 87 '''查找C,C++代碼''' 88 pattern_code_cpp = 'class="cpp">([\s\S]*?)</pre>' 89 code = re.search(pattern_code_cpp, response) 90 if code is not None and code.group(1).find('include') != -1: 91 print(str(pid) + 'cpp, 已找到!') 92 '''對代碼中html元素進行處理''' 93 code = '0' + code.group(1) 94 code = translate_code(code) 95 return code 96 97 '''查找JAVA代碼''' 98 pattern_code_java = 'class="java">([\s\S]*?)</pre>' 99 code = re.search(pattern_code_java, response) 100 if code is not None and code.group(1).find('import') != -1: 101 print(str(pid) + 'java, 已找到!') 102 code = '5' + code.group(1) 103 code = translate_code(code) 104 return code 105 return ''
3)對代碼中的html元素處理
Compilation Error次數多了就知道什么元素沒處理了
1 # coding :utf-8 2 # @Time : 2017/8/7 19:08 3 # @Author : Yong-life 4 # @File : translate_code.py 5 6 def translate_code(code): 7 '''轉化代碼中的html元素''' 8 code = code.replace('<', '<') 9 code = code.replace('>', '>') 10 code = code.replace('"', '"') 11 code = code.replace('&', '&') 12 code = code.replace('+', '+') 13 code = code.replace(''', '\'') 14 code = code.replace(' ', ' ') 15 code = code.replace(' ', ' ') 16 '''替換''' 17 code = code.replace('</pre>', ' ') 18 code = code.replace('</div>', ' ') 19 code = code.replace('<pre>', ' ') 20 code = code.replace('<div>', ' ') 21 code = code.replace('</span>', ' ') 22 23 return code
4)根據不同語言,選擇不同編譯器提交代碼,並檢查代碼結果
我們爬取了c,c++,java的代碼,提交代碼時,要注意選擇編譯器,我們需要通過提交不同的代碼查看請求參數的不同。
這里推薦一款抓包軟件:fiddler,小型,實用
關於fiddler的說明:使用fiddler進行抓包測試
測試后,發現:
c:3
c++: 2
G++: 0
JAVA: 5
OK!那么我們在post提交時,修改language參數就可以實現使用不同的編譯器進行提交了。提交后,遞歸檢查提交結果。
檢查結果時:注意author必須是賬戶名,用昵稱是搜不到
1 # coding :utf-8 2 # @Time : 2017/8/7 15:00 3 # @Author : Yong-life 4 # @File : submit_codes.py 5 6 import requests 7 from sqlite_hdu import Sql 8 from headers_cookies import * 9 from translate_code import translate_code 10 import re 11 import time 12 13 14 class SubmitCode(): 15 '''題目號,代碼''' 16 17 def __init__(self, pid, code): 18 self.cu = Sql() 19 self.pid = pid 20 self.code = code 21 22 def submit_manager(self): 23 24 print("提交代碼,休息3秒: " + str(self.pid)) 25 self.submit(self.pid, self.code) 26 '''提交代碼,休息3秒''' 27 time.sleep(3) 28 29 if self.query_result(self.pid): 30 print("題號: " + str(self.pid) + "已AC") 31 self.cu.sql_close() 32 return True 33 else: 34 print("題號: " + str(self.pid) + "WA ,正在繼續提交!") 35 self.cu.sql_close() 36 return False 37 38 '''提交代碼''' 39 40 def submit(self, pid, code): 41 url = 'http://acm.hdu.edu.cn/submit.php?action=submit' 42 try: 43 data = { 44 'check': '0', 45 'problemid': str(pid), 46 'language': code[0], 47 'usercode': code[1:] 48 } 49 except IndexError: 50 print("empty code") 51 return 52 response = requests.post(url, data, headers=get_headers(), cookies=get_cookie_from_chrome()) 53 54 '''檢查提交結果''' 55 56 def query_result(self, pid): 57 58 USER_NAME = self.get_user_name() 59 '''請求url''' 60 url = 'http://acm.hdu.edu.cn/status.php?first=&pid=' + str(pid) + '&user=' + USER_NAME + '&lang=0&status=0' 61 response = requests.get(url, headers=get_headers(), cookies=get_cookie_from_chrome()) 62 63 '''檢查結果''' 64 pattern_query = r'<td><font color=red>(.*?)</font>' 65 query_result = re.findall(pattern_query, response.text) 66 if len(query_result) > 0: 67 '''AC''' 68 return True 69 else: 70 '''判斷代碼提交代碼狀態''' 71 '''是否在判題中''' 72 if response.text.find("Queuing") != -1 or response.text.find("Running") != -1 or response.text.find( 73 "Compiling") != -1: 74 '''等待判題中,暫不提交''' 75 print('等待判題') 76 time.sleep(2) 77 return self.query_result(pid) 78 else: 79 '''代碼WA''' 80 return False 81 82 '''爬取賬號名,賬號名和昵稱,只能用賬號名查找結果''' 83 84 def get_user_name(self): 85 url = 'http://acm.hdu.edu.cn/' 86 response = requests.get(url, headers=get_headers(), cookies=get_cookie_from_chrome()).text 87 pattern_user_name = r'width:150px"><a href="/userstatus.php\?user=(.*?)"' 88 user_name = re.search(pattern_user_name, response).group(1) 89 90 return user_name
5)將AC代碼存入數據庫
注意:sql查找語句,傳值必須是元組的形式,即使只有一個值,要加‘,’
1 # coding :utf-8 2 # @Time : 2017/8/7 15:42 3 # @Author : Yong-life 4 # @File : sqlite_hdu.py 5 6 import sqlite3 7 8 9 class Sql(): 10 def __init__(self): 11 file_path = 'E:\python_workspace\crawling_hdu\sql_databases\hdu.db' 12 self.con = sqlite3.connect(file_path, check_same_thread=False) # 支持多線程訪問 13 self.cu = self.con.cursor() 14 # self.cu.execute('DROP TABLE IF EXISTS problem') 15 self.cu.execute("CREATE TABLE IF NOT EXISTS problem (pid INTEGER PRIMARY KEY , status INT, code TEXT)") 16 17 def insert_msg(self, pid, status=0, code=''): 18 '''插入信息''' 19 value = (pid, status, code) 20 self.cu.execute("INSERT INTO problem(pid, status, code) VALUES (?,?,?)", value) 21 self.con.commit() 22 23 def query_code(self, pid): 24 '''查找代碼''' 25 self.cu.execute("SELECT code FROM problem WHERE pid=?", (pid,)) # why 26 return self.cu.fetchone()[0] 27 28 def query_pid(self, pid): 29 '''檢查代碼是否已存庫''' 30 self.cu.execute("SELECT pid FROM problem WHERE pid=?", (pid,)) # why 31 return self.cu.fetchone() 32 33 def delete_problem_msg(self, pid): 34 '''刪除''' 35 self.cu.execute("DELETE FROM problem WHERE pid=?", (pid,)) # why 36 self.con.commit() 37 38 def update_problem_code(self, pid, code): 39 '''更新代碼''' 40 self.cu.execute("UPDATE problem SET code=? WHERE pid=?", (code, pid)) 41 self.con.commit() 42 43 def sql_close(self): 44 self.cu.close() 45 self.con.close()
6)cookie與header的獲取:
cookie可以從google的chrome瀏覽器獲取,路徑在:“C:\Users\Garbos\AppData\Local\Google\Chrome\User Data\Default\Cookies”,對應改一下用戶名就好了
1 # coding :utf-8 2 # @Time : 2017/7/30 16:42 3 # @Author : Jingxiao Fu 4 # @File : headers_cookies.py 5 import random 6 import os 7 import subprocess 8 import sqlite3 9 import win32crypt 10 import sys 11 12 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 13 Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0) 14 Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)''' 15 16 17 def get_headers(): 18 header = header_str.split('\n') 19 header_length = len(header) 20 headers = {'user-agent': header[random.randint(0, header_length - 1)]} 21 return headers 22 23 24 def get_cookie_use_hand(): 25 '''手動復制cookie''' 26 cookie = "Cookie:exesubmitlang=0; PHPSESSID=uqfje2cajo7j3kicrn8d2fol25" 27 28 cookie = cookie.replace('Cookie:', '') 29 cookie = cookie.replace(' ', '') 30 cookies = cookie.split(';') 31 for i in range(len(cookies)): 32 cookies[i] = cookies[i].replace('=', ':', 1) 33 cookies_dict = {} 34 for header in cookies: 35 L = header.split(':', 1) 36 cookies_dict[L[0]] = L[1] 37 return cookies_dict 38 39 40 '''從chrome瀏覽器獲取cookie''' 41 42 43 def get_cookie_from_chrome(): 44 host_url = 'acm.hdu.edu.cn' 45 cookie_file_path = r"C:\Users\Garbos\AppData\Local\Google\Chrome\User Data\Default\Cookies" 46 47 sql_query = "select host_key, name, encrypted_value ,value from cookies WHERE host_key='%s'" % host_url 48 with sqlite3.connect(cookie_file_path) as con: 49 cu = con.cursor() 50 cu.execute(sql_query) 51 cookies_sql = {name: win32crypt.CryptUnprotectData(encrypted_value)[1].decode() for 52 host_key, name, encrypted_value, value in cu.execute(sql_query).fetchall()} 53 54 return cookies_sql
開始你的AK之旅吧!