轉載註明原地址:http://blog.csdn.net/nk_test/article/details/49497017
少年,作為苦練ACM,通宵刷題的你 是不是想著有一天能夠榮登各大OJ榜首,俯瞰芸芸眾生,唔....要做到這件事情可是需要一定天賦的哦。
博主本身也搞過一段時間的acm,對刷題深有感觸,不信可以去看我部落格的acm題解(哈哈)。
不過,先給各位辛苦刷題的ACMer賠個不是,畢竟這是很投機的一種方式,僅供娛樂,還請各位見諒。
受學長的啟蒙,打算自己做一個使用C++語言完成的自動刷題神器,也可以叫自動AC機(什麼。ac自動機...嚇尿),先來看一下成果:
(註:這是第一次刷完後的排名,後來對代碼進行了很大的最佳化,但是由於時間關係,沒有再刷一遍,否則肯定進入前十。)
第17名是我,AC率還算不錯吧,但是我最佳化之後的肯定比這個高。
好了,扯淡完畢,下面進入本文,先來說一下整體思路:
1)使用socket編程類比HTTP協議GET請求向伺服器發送頁面請求
2)藉助搜尋引擎找到相關題目的代碼(一般csdn的居多)
3)使用Regex解析HTML代碼擷取部落格串連,緊接著從部落格中解析出 題目的代碼
4)對代碼進行編碼轉換的處理,模仿HTTP協議的POST請求向伺服器提交代碼
5)解析提交後返回的State頁面,提取最終的結果(是否Accepted)、耗時和空間佔用。
6)將刷題過程儲存至SQL Server資料庫,供以後的資料分析。
是不是感覺很簡單的樣子,讓我們一步一步來。
(一)使用socket編程類比HTTP協議GET請求向伺服器發送頁面請求
我們在baidu中搜尋索引鍵,點擊按鈕,伺服器會返回我們一個頁面,這件事情使用程式該如何?呢。
答案就是我們使用Socket編程通過bind(),connect(),send(),recv()這些函數建立與伺服器的串連。這些知識就不再這裡展開了,讀者可以自行baidu或者參考我的Linux網路編程專欄。 接下來我們想:點擊按鈕的過程發生了什麼,我們使用send()需要將什麼資訊發送至伺服器,這裡就要涉及到HTTP協議的GET請求。
我們只需要實現GET的要求標頭即可(可以通過chrome按F12來查看),注意和本文之間有一個空行,即/r/n
[cpp] view plain copy //向伺服器發送GET請求 string reqInfo = "GET " + (string)othPath + " HTTP/1.1\r\nHost: " + (string)host + "\r\nConnection:Close\r\n\r\n"; if (SOCKET_ERROR == send(sock, reqInfo.c_str(), reqInfo.size(), 0)) { cout << "send error! 錯誤碼: " << WSAGetLastError() << endl; closesocket(sock); } (二)藉助搜尋引擎擷取csdn部落格連結
一開始的時候我想利用百度搜尋引擎,但是發現返回到HTML頁面中根本無法找出csdn部落格的連結特徵,後來發現,原來是baidu為了避免爬蟲爬取進行了加密處理,如下圖:
注意左下角是第一個csdn部落格的串連....坑了我一段時間。
後來發現360搜尋沒有加密,所以那就用360吧。。提取出來放入vector儲存,然後使用C++11的Regex解析出csdn部落格的地址。
[cpp] view plain copy void regexGetcom(string &allHtml) //提取網頁中的csdn部落格的url { blogUrl.clear(); smatch mat; regex pattern("href=\"(http://blog.csdn[^\\s\"]+)\""); string::const_iterator start = allHtml.begin(); string::const_iterator end = allHtml.end(); while (regex_search(start, end, mat, pattern)) { string msg(mat[1].first, mat[1].second); blogUrl.push_back(msg); start = mat[0].second; } }
(三)從HTML中解析出代碼
注意代碼一般由#include開始,結束的位置是</textarea>或者</pre>,我們利用這個特徵進行提取。
[cpp] view plain copy void GetCode(string &allHtml) { CodeHtml = ""; int pos = allHtml.find("#include"); if (pos != string::npos) { for (int i = pos; i < allHtml.length(); i++) { if ((allHtml[i] == '<'&&allHtml[i + 1] == '/'&&allHtml[i + 2] == 't'&&allHtml[i + 3] == 'e'&&allHtml[i + 4] == 'x'&&allHtml[i + 5] == 't') || (allHtml[i] ==