转载注明原地址: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. 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中解析出代码

注意代码一般由#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(),页面还没有显示出结果,对,我比较懒==

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

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

手把手教你用C++ 写ACM自动刷题神器(冲入HDU首页)的更多相关文章

  1. HDU 自动刷题机 Auto AC (轻轻松松进入HDU首页)

    前言: 在写这篇文章之前,首先感谢给我思路以及帮助过我的学长们 以下4篇博客都是学长原创,其中有很多有用的,值得学习的东西,希望能够帮到大家! 1.手把手教你用C++ 写ACM自动刷题神器(冲入HDU ...

  2. 手把手教你从零写一个简单的 VUE

    本系列是一个教程,下面贴下目录~1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 今天给大家带来的是实现一个简单的类似 VUE 一样的前端框架,VUE 框架现在应 ...

  3. 手把手教你从零写一个简单的 VUE--模板篇

    教程目录1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 Hello,我又回来了,上一次的文章教会了大家如何书写一个简单 VUE,里面实现了VUE 的数据驱动视图 ...

  4. 教你用python写:HDU刷题神器

    声明:本文以学习为目的,请不要影响他人正常判题 HDU刷题神器,早已被前辈们做出来了,不过没有见过用python写的.大一的时候见识了学长写这个,当时还是一脸懵逼,只知道这玩意儿好屌-.时隔一年,决定 ...

  5. 【BZOJ-4590】自动刷题机 二分 + 判定

    4590: [Shoi2015]自动刷题机 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 156  Solved: 63[Submit][Status ...

  6. BZOJ4590 自动刷题机

    Description 曾经发明了信号增幅仪的发明家SHTSC又公开了他的新发明:自动刷题机--一种可以自动AC题目的神秘装置.自动 刷题机刷题的方式非常简单:首先会瞬间得出题目的正确做法,然后开始写 ...

  7. BZOJ_4590_[Shoi2015]自动刷题机_二分答案

    BZOJ_4590_[Shoi2015]自动刷题机_二分答案 Description 曾经发明了信号增幅仪的发明家SHTSC又公开了他的新发明:自动刷题机--一种可以自动AC题目的神秘装置.自动 刷题 ...

  8. BZOJ4590 Shoi2015 自动刷题机 【二分】

    BZOJ4590 Shoi2015 自动刷题机 Description 曾经发明了信号增幅仪的发明家SHTSC又公开了他的新发明:自动刷题机–一种可以自动AC题目的神秘装置.自动刷题机刷题的方式非常简 ...

  9. 【BZOJ4590】[Shoi2015]自动刷题机 二分

    [BZOJ4590][Shoi2015]自动刷题机 Description 曾经发明了信号增幅仪的发明家SHTSC又公开了他的新发明:自动刷题机--一种可以自动AC题目的神秘装置.自动刷题机刷题的方式 ...

随机推荐

  1. D3D9 GPU Hacks (转载)

    D3D9 GPU Hacks I’ve been trying to catch up what hacks GPU vendors have exposed in Direct3D9, and tu ...

  2. Windows平台注册mysql服务

    将mysql 注册为服务: ->mysqld --install 卸载服务: ->mysqld --remove 命令行启动mysql: ->net start mysql 命令行关 ...

  3. Java基础之线程——管理线程同步代码块(BankOperation4)

    控制台程序. 除了同步类对象的方法之外,还可以把程序中的语句或代码块制定为synchronized,这种方式更强大,因为可以指定哪个对象从语句或代码块的同步中获益,而不像同步方法那样仅仅是包含代码的对 ...

  4. Ruby操作Excel的方法与技巧大全

    测试工作中,批量的数据通常会放到excel表格中,测试输出的数据写回表格中,这样输入输出易于管理,同时清晰明了 使用ruby来操作excel文件首先需要在脚本里包含以下语句 require'win32 ...

  5. Java基础(54):java四种内部类详解(转)

    一般来说,有4中内部类:常规内部类.静态内部类.局部内部类.匿名内部类.  一.常规内部类:常规内部类没有用static修饰且定义在在外部类类体中. 1.常规内部类中的方法可以直接使用外部类的实例变量 ...

  6. Java获取当前内存及硬盘使用情况

    import java.io.File; import java.lang.management.ManagementFactory; import com.sun.management.Operat ...

  7. Union与union all区别

    Union因为要进行重复值扫描,所以效率低.如果合并没有刻意要删除重复行,那么就使用Union All  两个要联合的SQL语句 字段个数必须一样,而且字段类型要“相容”(一致): 如果我们需要将两个 ...

  8. 夺命雷公狗—angularjs—22—bind改指向和传参方式

    在angularjs中的传参的jquery的方式是极度相似的噢,而且还可以通过bind来改变指向 <!DOCTYPE html> <html lang="en" ...

  9. DIB位图文件的格式、读取、保存和显示(转载)

    一.位图文件结构 位图文件由三部分组成:文件头 + 位图信息 + 位图像素数据 1.位图文件头:BitMapFileHeader.位图文件头主要用于识别位图文件.以下是位图文件头结构的定义: type ...

  10. UIView的ContentMode

    UIViewContentMode   typedef enum {    UIViewContentModeScaleToFill,    UIViewContentModeScaleAspectF ...