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

转载注明原地址:http://blog.csdn.net/nk_test/article/details/49497017

少年,作为苦练ACM,通宵刷题的你 是不是想着有一天能够荣登各大OJ榜首,俯瞰芸芸众生,唔....要做到这件事情可是需要一定天赋的哦!

博主本身也搞过一段时间的acm,对刷题深有感触,不信可以去看我博客的acm题解(哈哈)。

不过,先给各位辛苦刷题的ACMer赔个不是,毕竟这是很投机的一种方式,仅供娱乐,还请各位见谅!

受学长的启蒙,打算自己做一个使用C++语言完成的自动刷题神器,也可以叫自动AC机(什么?ac自动机...吓尿),先来看一下成果:

(注:这是第一次刷完后的排名,后来对代码进行了很大的优化,但是由于时间关系,没有再刷一遍,否则肯定进入前十!)

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

第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为了避免爬虫爬取进行了加密处理,如下图:

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

注意左下角是第一个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. string::const_iterator start = allHtml.begin();
  7. string::const_iterator end = allHtml.end();
  8. while (regex_search(start, end, mat, pattern))
  9. {
  10. string msg(mat[1].first, mat[1].second);
  11. blogUrl.push_back(msg);
  12. start = mat[0].second;
  13. }
  14. }

(三)从HTML中解析出代码

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

注意代码一般由#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. 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] == '>'))
  10. {
  11. return ;
  12. }
  13. CodeHtml += allHtml[i];
  14. }
  15. }
  16. else
  17. {
  18. cout << "未找到合适的代码!" << endl;
  19. return;
  20. }
  21. }

(四)对代码进行编码转换的处理,模仿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. string ASCtoHex(int num) //十进制转换成十六进制
  52. {
  53. char str[] = "0123456789ABCDEF";
  54. int temp=num;
  55. string ans;
  56. while (temp)
  57. {
  58. ans += str[temp % 16];
  59. temp /= 16;
  60. }
  61. ans += '%';
  62. reverse(ans.begin(), ans.end());
  63. return ans;
  64. }
  65. string GetRescode(string &CodeHtml)
  66. {
  67. ResCode = "";
  68. for (int i = 0; i < CodeHtml.length(); i++)
  69. {
  70. //if (!isdigit(unsigned(CodeHtml[i])) && !isalpha(unsigned(CodeHtml[i])))
  71. 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))
  72. {
  73. //if (CodeHtml[i] == '\r' && (i + 1) < CodeHtml.length() && CodeHtml[i + 1] == '\n')
  74. //if (CodeHtml[i] == '\r')
  75. //{
  76. //  ResCode += "++%0D";
  77. //
  78. //}
  79. //else if (CodeHtml[i] == '\n')
  80. if (CodeHtml[i] == '\n')
  81. {
  82. ResCode += "%0D%0A";
  83. }
  84. else if (CodeHtml[i] == '.' || CodeHtml[i] == '-' || CodeHtml[i] == '*')
  85. ResCode += CodeHtml[i];
  86. /*if (CodeHtml[i] == 10)
  87. ResCode += "%0D%0A";
  88. */
  89. /*else if (CodeHtml[i] >= 0xB0 && (i + 1)<CodeHtml.length()&&CodeHtml[i + 1] >= 0xA1)//判断汉字
  90. {
  91. i++;
  92. ResCode += CodeHtml[i];
  93. ResCode += CodeHtml[i + 1];
  94. }*/
  95. else
  96. {
  97. string cur = ASCtoHex(CodeHtml[i]);
  98. if (cur == "%9")
  99. ResCode += "++++";
  100. else if (cur == "%20")
  101. ResCode += '+';
  102. else if (cur == "%D")
  103. ResCode += "++";
  104. else
  105. ResCode += cur;
  106. }
  107. }
  108. else
  109. ResCode += CodeHtml[i];
  110. }
  111. 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. //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";
  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=";
  6. ElseInfo = ElseInfo+ (string)s + "\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: zh-CN,zh;q=0.8";
  7. //向服务器发送POST请求
  8. string HeaderP="check=0&problemid="+(string)s;
  9. HeaderP += "&language=2&usercode=";
  10. ResCode = HeaderP + ResCode;
  11. char s[300];
  12. _itoa( ResCode.length(), s, 10);  /////??????
  13. string Cookie = "exesubmitlang=2; PHPSESSID=8qdqoujc8ptncdb518cksqr687; CNZZDATA1254072405=385429082-1445151305-http%253A%252F%252Facm.hdu.edu.cn%252F%7C1446089001";
  14. 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;
  15. if (SOCKET_ERROR == send(sock, reqInfo.c_str(), reqInfo.size(), 0))
  16. {
  17. cout << "send error! 错误码: " << WSAGetLastError() << endl;
  18. closesocket(sock);
  19. }

(五)解析提交后返回的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. Tpos += 9;
  30. while (1)
  31. {
  32. if (allHtml[Tpos] == '<')
  33. break;
  34. StateTime += allHtml[Tpos];
  35. Tpos++;
  36. }
  37. cout << "使用的时间为:" << StateTime << endl;
  38. if (pos == string::npos)
  39. return;
  40. else
  41. {
  42. pos = pos - 52;
  43. int begin;
  44. while (1)
  45. {
  46. if (allHtml[pos] == '>')
  47. {
  48. begin = pos;
  49. break;
  50. }
  51. pos--;
  52. }
  53. for (int i = begin + 1;allHtml[i]!='<';i++)
  54. {
  55. StateAns += allHtml[i];
  56. }
  57. }
  58. cout << "最终的state界面的答案是:" << "---------------::::::" << StateAns << endl;
  59. }

(六)将刷题过程存储至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(),页面还没有显示出结果,对,我比较懒==

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

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

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

上一篇:记一次Spring aop的所遇到的问题


下一篇:ThinkPHP5.0最最最最最简单实例