教你用python寫:HDU刷題神器

聲明:本文以學習爲目的,請不要影響他人正常判題javascript

HDU刷題神器,早已被前輩們作出來了,不過沒有見過用python寫的。大一的時候見識了學長寫這個,當時仍是一臉懵逼,只知道這玩意兒好屌…。時隔一年,決定本身實現這個功能。php

 

刷到第13名,AC率不高,由於,搜索引擎搜到的結果,日後就很難找到正確的代碼了html

首先對辛苦刷題的acmer和hdu的管理員道歉,各位,抱歉。java

介紹總體思路python

  1. 總體用多線程:線程執行從爬代碼到提交的所有過程
  2. 分層次:對搜索引擎搜索的結果,進行劃分,分層爬取

局部思路c++

  1. 爬取搜索引擎獲得的與題目相關的url,獲得url_list
  2. 爬取url_list中的url,扒到代碼就提交
  3. 檢查提交結果,WA以後繼續爬取url_list中的代碼
  4. 循環,直到列表爲空或者AC

相關模塊sql

  1. threadpool線程池,分配線程任務,多線程併發提交代碼
  2. 用requests模塊發送請求
  3. 正則爬取url和代碼
  4. Sqlite存放AC代碼(打表啊,再申請個帳號從數據庫中提交代碼100%AC)

 1)採用線程池實現多線程,注意控制最大併發數量chrome

搜索引擎使用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任務

  1. 爬取crawl_level_begin-crawl_level頁搜索結果的url
  2. 按照url_list爬取代碼
  3. 提交代碼

 每輪爬完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('&lt;', '<')
 9     code = code.replace('&gt;', '>')
10     code = code.replace('&quot;', '"')
11     code = code.replace('&amp;', '&')
12     code = code.replace('&#43;', '+')
13     code = code.replace('&#39;', '\'')
14     code = code.replace('&nbsp;', ' ')
15     code = code.replace('&#160;', ' ')
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之旅吧!

相關文章
相關標籤/搜索