小坏蛋_千千

I was caught in a heavy rain!

手把手教你用C++ 写ACM自动刷题神器(冲入HDU首页)

转载注明原地址: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)使用正则表达式解析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
  1. //向服务器发送GET请求   
  2.     string  reqInfo = "GET " + (string)othPath + " HTTP/1.1\r\nHost: " + (string)host + "\r\nConnection:Close\r\n\r\n";  
  3.     if (SOCKET_ERROR == send(sock, reqInfo.c_str(), reqInfo.size(), 0))  
  4.     {  
  5.         cout << "send error! 错误码: " << WSAGetLastError() << endl;  
  6.         closesocket(sock);  
  7.     }  
(二)借助搜索引擎获取csdn博客链接

一开始的时候我想利用百度搜索引擎,但是发现返回到HTML页面中根本无法找出csdn博客的链接特征,后来发现,原来是baidu为了避免爬虫爬取进行了加密处理,如下图:

注意左下角是第一个csdn博客的连接....坑了我一段时间。

后来发现360搜索没有加密,所以那就用360吧。。提取出来放入vector保存,然后使用C++11的正则表达式解析出csdn博客的地址。

[cpp] view plain copy
  1. void regexGetcom(string &allHtml) //提取网页中的csdn博客的url    
  2. {  
  3.     blogUrl.clear();  
  4.     smatch mat;  
  5.     regex pattern("href=\"(http://blog.csdn[^\\s\"]+)\"");  
  6.       
  7.     string::const_iterator start = allHtml.begin();  
  8.     string::const_iterator end = allHtml.end();  
  9.     while (regex_search(start, end, mat, pattern))  
  10.     {  
  11.         string msg(mat[1].first, mat[1].second);  
  12.         blogUrl.push_back(msg);  
  13.         start = mat[0].second;  
  14.     }  
  15. }  

(三)从HTML中解析出代码


注意代码一般由#include开始,结束的位置是</textarea>或者</pre>,我们利用这个特征进行提取。

[cpp] view plain copy
  1. void GetCode(string &allHtml)  
  2. {  
  3.     CodeHtml = "";  
  4.     int pos = allHtml.find("#include");  
  5.     if (pos != string::npos)  
  6.     {  
  7.         for (int i = pos; i < allHtml.length(); i++)  
  8.         {  
  9.               
  10.             if ((allHtml[i] == '<'&&allHtml[i + 1] == '/'&&allHtml[i + 2] == 't'&&allHtml[i + 3] == 'e'&&allHtml[i + 4] == 'x'&&allHtml[i + 5] == 't') || (allHtml[i] == '<'&&allHtml[i + 1] == '/'&&allHtml[i + 2] == 'p'&&allHtml[i + 3] == 'r'&&allHtml[i + 4] == 'e'&&allHtml[i + 5] == '>'))  
  11.             {  
  12.                 return ;  
  13.             }  
  14.             CodeHtml += allHtml[i];  
  15.         }  
  16.     }  
  17.     else  
  18.     {  
  19.         cout << "未找到合适的代码!" << endl;  
  20.         return;  
  21.     }  
  22.       
  23. }  

(四)对代码进行编码转换的处理,模仿HTTP协议的POST请求向服务器提交代码

我们可以看到上面的代码并不是解析出来就能用的,还包含有&lt,&gt等HTML的元素,并且还有汉字转码的问题需要我们需要处理。POST的时候,还需要考虑HTTP编码,

将空格回车等转换为十六进制发送提交,不说了,直接看代码:

[cpp] view plain copy
  1. string ReplaceDiv(string &CodeHtml)  
  2. {  
  3.     string ans;  
  4.     for (int i = 0; i < CodeHtml.length(); i++)  
  5.     {  
  6.         if (CodeHtml[i] == '&'&&CodeHtml[i + 1] == 'l'&&CodeHtml[i + 2] == 't'&&CodeHtml[i + 3] == ';')  
  7.         {  
  8.             ans += '<';  
  9.             i += 3;  
  10.         }  
  11.         else if (CodeHtml[i] == '&'&&CodeHtml[i + 1] == 'g'&&CodeHtml[i + 2] == 't'&&CodeHtml[i + 3] == ';')  
  12.         {  
  13.             ans += '>';  
  14.             i += 3;  
  15.         }  
  16.         else if (CodeHtml[i] == '/'&&CodeHtml[i + 1] == 'n')  
  17.         {  
  18.             ans += "\\n";  
  19.             i +=1;  
  20.         }  
  21.         else if (CodeHtml[i] == '&'&&CodeHtml[i + 1] == 'a'&&CodeHtml[i + 2] == 'm'&&CodeHtml[i + 3] == 'p'&&CodeHtml[i + 4] == ';')  
  22.         {  
  23.             ans += '&';  
  24.             i += 4;  
  25.         }  
  26.         else if (CodeHtml[i] == '&'&&CodeHtml[i + 1] == 'q'&&CodeHtml[i + 2] == 'u'&&CodeHtml[i + 3] == 'o'&&CodeHtml[i + 4] == 't'&&CodeHtml[i + 5] == ';')  
  27.         {  
  28.             ans += '\"';  
  29.             i += 5;  
  30.         }  
  31.         else if (CodeHtml[i] == '&'&&CodeHtml[i + 1] == 'n'&&CodeHtml[i + 2] == 'b'&&CodeHtml[i + 3] == 's'&&CodeHtml[i + 4] == 'p'&&CodeHtml[i + 5] == ';')  
  32.         {  
  33.             ans += ' ';  
  34.             i += 5;  
  35.         }  
  36.         else if (CodeHtml[i] == '&'&&CodeHtml[i + 1] == '#'&&CodeHtml[i + 2] == '4'&&CodeHtml[i + 3] == '3'&&CodeHtml[i + 4] == ';')  
  37.         {  
  38.             ans += '+';  
  39.             i += 4;  
  40.         }  
  41.         else if (CodeHtml[i] == '&'&&CodeHtml[i + 1] == '#'&&CodeHtml[i + 2] == '3'&&CodeHtml[i + 3] == '9'&&CodeHtml[i + 4] == ';')  
  42.         {  
  43.             ans += '\'';  
  44.             i += 4;  
  45.         }  
  46.         else  
  47.             ans += CodeHtml[i];  
  48.     }  
  49.     return ans;  
  50. }  
  51.   
  52. string ASCtoHex(int num) //十进制转换成十六进制  
  53. {  
  54.     char str[] = "0123456789ABCDEF";  
  55.     int temp=num;  
  56.     string ans;  
  57.     while (temp)  
  58.     {  
  59.         ans += str[temp % 16];  
  60.         temp /= 16;  
  61.     }  
  62.     ans += '%';  
  63.     reverse(ans.begin(), ans.end());  
  64.     return ans;  
  65. }  
  66. string GetRescode(string &CodeHtml)  
  67. {  
  68.     ResCode = "";  
  69.     for (int i = 0; i < CodeHtml.length(); i++)  
  70.     {  
  71.             //if (!isdigit(unsigned(CodeHtml[i])) && !isalpha(unsigned(CodeHtml[i])))  
  72.             if ((CodeHtml[i] >= 0 && CodeHtml[i] < 48) || (CodeHtml[i]>57 && CodeHtml[i]<65) || (CodeHtml[i]>90 && CodeHtml[i]<97) || (CodeHtml[i]>122 & CodeHtml[i] <= 127))  
  73.             {  
  74.                 //if (CodeHtml[i] == '\r' && (i + 1) < CodeHtml.length() && CodeHtml[i + 1] == '\n')  
  75.                 //if (CodeHtml[i] == '\r')  
  76.                 //{  
  77.                 //  ResCode += "++%0D";  
  78.                 //  
  79.                 //}  
  80.                 //else if (CodeHtml[i] == '\n')  
  81.                 if (CodeHtml[i] == '\n')  
  82.                 {  
  83.                     ResCode += "%0D%0A";  
  84.                 }  
  85.                 else if (CodeHtml[i] == '.' || CodeHtml[i] == '-' || CodeHtml[i] == '*')  
  86.                     ResCode += CodeHtml[i];  
  87.   
  88.                 /*if (CodeHtml[i] == 10) 
  89.                     ResCode += "%0D%0A"; 
  90.                     */  
  91.                 /*else if (CodeHtml[i] >= 0xB0 && (i + 1)<CodeHtml.length()&&CodeHtml[i + 1] >= 0xA1)//判断汉字 
  92.                 { 
  93.                 i++; 
  94.                 ResCode += CodeHtml[i]; 
  95.                 ResCode += CodeHtml[i + 1]; 
  96.                 }*/  
  97.                 else  
  98.                 {  
  99.                     string cur = ASCtoHex(CodeHtml[i]);  
  100.                     if (cur == "%9")  
  101.                         ResCode += "++++";  
  102.                     else if (cur == "%20")  
  103.                         ResCode += '+';  
  104.                     else if (cur == "%D")  
  105.                         ResCode += "++";  
  106.                     else  
  107.                         ResCode += cur;  
  108.                 }  
  109.   
  110.             }  
  111.             else  
  112.                 ResCode += CodeHtml[i];  
  113.   
  114.         }  
  115.           
  116.     return ResCode;  
[cpp] view plain copy
  1. //UTF-8到GB2312的转换  
  2. char* U2G(const char* utf8)  
  3. {  
  4.     int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);  
  5.     wchar_t* wstr = new wchar_t[len + 1];  
  6.     memset(wstr, 0, len + 1);  
  7.     MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wstr, len);  
  8.     len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL);  
  9.     char* str = new char[len + 1];  
  10.     memset(str, 0, len + 1);  
  11.     WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, len, NULL, NULL);  
  12.     if (wstr) delete[] wstr;  
  13.     return str;  
  14. }  

POST:注意头信息要全,并且Cookie要写你自己的,一旦浏览器关闭就会失效,要重置

[cpp] view plain copy
  1. string  Typee = "\r\nContent-Type: application/x-www-form-urlencoded";  
  2.     string ConLen = "\r\nContent-Length: ";  
  3.     _itoa(ProblemID, s, 10);  
  4.   
  5.     //string ElseInfo = "\r\nCache-Control: max-age=0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8O\r\nOrigin: http://acm.hdu.edu.cn\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36\r\nReferer: http://acm.hdu.edu.cn/submit.php?pid=1003\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: zh-CN,zh;q=0.8";  
  6.     string ElseInfo = "\r\nCache-Control: max-age=0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8O\r\nOrigin: http://acm.hdu.edu.cn\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36\r\nReferer: http://acm.hdu.edu.cn/submit.php?pid=";  
  7.     ElseInfo = ElseInfo+ (string)s + "\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: zh-CN,zh;q=0.8";  
  8.     //向服务器发送POST请求   
  9.       
  10.     string HeaderP="check=0&problemid="+(string)s;  
  11.     HeaderP += "&language=2&usercode=";  
  12.       
  13.     ResCode = HeaderP + ResCode;  
  14.     char s[300];  
  15.     _itoa( ResCode.length(), s, 10);  /////??????  
  16.     string Cookie = "exesubmitlang=2; PHPSESSID=8qdqoujc8ptncdb518cksqr687; CNZZDATA1254072405=385429082-1445151305-http%253A%252F%252Facm.hdu.edu.cn%252F%7C1446089001";  
  17.     string  reqInfo = "POST " + (string)othPath + " HTTP/1.1\r\nHost: " + (string)host + ElseInfo+ Typee + ConLen + (string)s + "\r\nCookie: " + Cookie + "\r\nConnection:Close\r\n\r\n" + ResCode;  
  18.   
  19.     if (SOCKET_ERROR == send(sock, reqInfo.c_str(), reqInfo.size(), 0))  
  20.     {  
  21.         cout << "send error! 错误码: " << WSAGetLastError() << endl;  
  22.         closesocket(sock);  
  23.     }  

(五)解析提交后返回的State页面,提取最终的结果(是否Accepted)、耗时和空间占用

这个就比较简单了,数据分析,可能实现都不一样,我是先定位的题号:

[cpp] view plain copy
  1. void GetResult(string &allHtml,int Prob)  //解析出state.php中的结果,空间,时间  
  2. {  
  3.      StateAns="";  
  4.      StateSapce="";  
  5.      StateTime="";  
  6.     char d[200];  
  7.     _itoa(ProblemID, d, 10);  
  8.     strcat(d, "</a>");  
  9.     int pos = allHtml.find((string)d);  
  10.     int Mpos = pos;  
  11.     int Tpos;  
  12.     if (Mpos == string::npos)  
  13.         return;  
  14.     else  
  15.     {  
  16.         Mpos += 17;  
  17.         while (1)  
  18.         {  
  19.             if (allHtml[Mpos] == '<')  
  20.             {  
  21.                 Tpos = Mpos;  
  22.                 break;  
  23.             }  
  24.             StateSapce += allHtml[Mpos];  
  25.             Mpos++;  
  26.         }  
  27.         cout << "使用的空间大小为:" << StateSapce << endl;  
  28.   
  29.     }  
  30.     Tpos += 9;  
  31.     while (1)  
  32.     {  
  33.         if (allHtml[Tpos] == '<')  
  34.             break;  
  35.         StateTime += allHtml[Tpos];  
  36.         Tpos++;  
  37.     }  
  38.     cout << "使用的时间为:" << StateTime << endl;  
  39.   
  40.     if (pos == string::npos)  
  41.         return;  
  42.     else  
  43.     {  
  44.         pos = pos - 52;  
  45.         int begin;  
  46.         while (1)  
  47.         {  
  48.             if (allHtml[pos] == '>')  
  49.             {  
  50.                 begin = pos;  
  51.                 break;  
  52.             }  
  53.             pos--;  
  54.         }  
  55.       
  56.         for (int i = begin + 1;allHtml[i]!='<';i++)  
  57.         {  
  58.             StateAns += allHtml[i];  
  59.         }  
  60.     }  
  61.     cout << "最终的state界面的答案是:" << "---------------::::::" << StateAns << endl;  
  62.   
  63.   
  64. }  

(六)将刷题过程存储至SQL Server数据库,供以后的数据分析

要点就是C++使用ado连接SQL Server数据库

[cpp] view plain copy
  1. CoInitialize(NULL);//初始化Com库  
  2.     _ConnectionPtr pMyConnect = NULL;//这是个对象指针,关于对象指针的内容可以百度一下,不过不理解也就算了  
  3.     HRESULT hr = pMyConnect.CreateInstance(__uuidof(Connection));  
  4.     //将对象指针实例化  
  5.     if (FAILED(hr))  
  6.     {  
  7.         cout << "_ConnectionPtr对象指针实例化失败!" << endl;  
  8.         return 0;  
  9.     }  
  10.     _bstr_t strConnect="Driver={sql server};server=Tach-PC\\SQLEXPRESS;uid=tach1;pwd=123456;database=ProblemSolved";  //SQLSERVER  
  11.     //这是连接到SQL SERVER数据库的连接字符串,其中的参数要自己改  
  12.     try{  
  13.         pMyConnect->Open(strConnect, """", NULL);   
  14.     }//连接到数据库,要捕捉异常  
  15.     catch (_com_error &e){  
  16.         cout << "连接数据库异常!" << endl;  
  17.         cout << e.ErrorMessage() << endl;  
  18.     }  
注意将上面的server名字,uid和pwd改为你自己的。

下图的Queuing请无视,因为我为了速度并没有Sleep(),页面还没有显示出结果,对,我比较懒==


好啦,到这里就大功告成啦!别刷太快哦,貌似被hdu封了一次IP。。。。。(囧)

最后项目的完整源码在我的Github,欢迎大家fork!

阅读更多
个人分类: HDU爬虫
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭