转载注明原地址: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
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的正则表达式解析出csdn博客的地址。
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>,我们利用这个特征进行提取。
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] == '<'&&allHtml[i + 1] == '/'&&allHtml[i + 2] == 'p'&&allHtml[i + 3] == 'r'&&allHtml[i + 4] == 'e'&&allHtml[i + 5] == '>'))
- {
- return ;
- }
- CodeHtml += allHtml[i];
- }
- }
- else
- {
- cout << "未找到合适的代码!" << endl;
- return;
- }
- }
(四)对代码进行编码转换的处理,模仿HTTP协议的POST请求向服务器提交代码
我们可以看到上面的代码并不是解析出来就能用的,还包含有<,>等HTML的元素,并且还有汉字转码的问题需要我们需要处理。POST的时候,还需要考虑HTTP编码,
将空格回车等转换为十六进制发送提交,不说了,直接看代码:
plain copy
- string ReplaceDiv(string &CodeHtml)
- {
- string ans;
- for (int i = 0; i < CodeHtml.length(); i++)
- {
- if (CodeHtml[i] == '&'&&CodeHtml[i + 1] == 'l'&&CodeHtml[i + 2] == 't'&&CodeHtml[i + 3] == ';')
- {
- ans += '<';
- i += 3;
- }
- else if (CodeHtml[i] == '&'&&CodeHtml[i + 1] == 'g'&&CodeHtml[i + 2] == 't'&&CodeHtml[i + 3] == ';')
- {
- ans += '>';
- i += 3;
- }
- else if (CodeHtml[i] == '/'&&CodeHtml[i + 1] == 'n')
- {
- ans += "\\n";
- i +=1;
- }
- else if (CodeHtml[i] == '&'&&CodeHtml[i + 1] == 'a'&&CodeHtml[i + 2] == 'm'&&CodeHtml[i + 3] == 'p'&&CodeHtml[i + 4] == ';')
- {
- ans += '&';
- i += 4;
- }
- else if (CodeHtml[i] == '&'&&CodeHtml[i + 1] == 'q'&&CodeHtml[i + 2] == 'u'&&CodeHtml[i + 3] == 'o'&&CodeHtml[i + 4] == 't'&&CodeHtml[i + 5] == ';')
- {
- ans += '\"';
- i += 5;
- }
- else if (CodeHtml[i] == '&'&&CodeHtml[i + 1] == 'n'&&CodeHtml[i + 2] == 'b'&&CodeHtml[i + 3] == 's'&&CodeHtml[i + 4] == 'p'&&CodeHtml[i + 5] == ';')
- {
- ans += ' ';
- i += 5;
- }
- else if (CodeHtml[i] == '&'&&CodeHtml[i + 1] == '#'&&CodeHtml[i + 2] == '4'&&CodeHtml[i + 3] == '3'&&CodeHtml[i + 4] == ';')
- {
- ans += '+';
- i += 4;
- }
- else if (CodeHtml[i] == '&'&&CodeHtml[i + 1] == '#'&&CodeHtml[i + 2] == '3'&&CodeHtml[i + 3] == '9'&&CodeHtml[i + 4] == ';')
- {
- ans += '\'';
- i += 4;
- }
- else
- ans += CodeHtml[i];
- }
- return ans;
- }
- string ASCtoHex(int num) //十进制转换成十六进制
- {
- char str[] = "0123456789ABCDEF";
- int temp=num;
- string ans;
- while (temp)
- {
- ans += str[temp % 16];
- temp /= 16;
- }
- ans += '%';
- reverse(ans.begin(), ans.end());
- return ans;
- }
- string GetRescode(string &CodeHtml)
- {
- ResCode = "";
- for (int i = 0; i < CodeHtml.length(); i++)
- {
- //if (!isdigit(unsigned(CodeHtml[i])) && !isalpha(unsigned(CodeHtml[i])))
- 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))
- {
- //if (CodeHtml[i] == '\r' && (i + 1) < CodeHtml.length() && CodeHtml[i + 1] == '\n')
- //if (CodeHtml[i] == '\r')
- //{
- // ResCode += "++%0D";
- //
- //}
- //else if (CodeHtml[i] == '\n')
- if (CodeHtml[i] == '\n')
- {
- ResCode += "%0D%0A";
- }
- else if (CodeHtml[i] == '.' || CodeHtml[i] == '-' || CodeHtml[i] == '*')
- ResCode += CodeHtml[i];
- /*if (CodeHtml[i] == 10)
- ResCode += "%0D%0A";
- */
- /*else if (CodeHtml[i] >= 0xB0 && (i + 1)<CodeHtml.length()&&CodeHtml[i + 1] >= 0xA1)//判断汉字
- {
- i++;
- ResCode += CodeHtml[i];
- ResCode += CodeHtml[i + 1];
- }*/
- else
- {
- string cur = ASCtoHex(CodeHtml[i]);
- if (cur == "%9")
- ResCode += "++++";
- else if (cur == "%20")
- ResCode += '+';
- else if (cur == "%D")
- ResCode += "++";
- else
- ResCode += cur;
- }
- }
- else
- ResCode += CodeHtml[i];
- }
- return ResCode;
plain copy
- //UTF-8到GB2312的转换
- char* U2G(const char* utf8)
- {
- int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
- wchar_t* wstr = new wchar_t[len + 1];
- memset(wstr, 0, len + 1);
- MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wstr, len);
- len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL);
- char* str = new char[len + 1];
- memset(str, 0, len + 1);
- WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, len, NULL, NULL);
- if (wstr) delete[] wstr;
- return str;
- }
POST:注意头信息要全,并且Cookie要写你自己的,一旦浏览器关闭就会失效,要重置:
plain copy
- string Typee = "\r\nContent-Type: application/x-www-form-urlencoded";
- string ConLen = "\r\nContent-Length: ";
- _itoa(ProblemID, s, 10);
- //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";
- 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=";
- ElseInfo = ElseInfo+ (string)s + "\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: zh-CN,zh;q=0.8";
- //向服务器发送POST请求
- string HeaderP="check=0&problemid="+(string)s;
- HeaderP += "&language=2&usercode=";
- ResCode = HeaderP + ResCode;
- char s[300];
- _itoa( ResCode.length(), s, 10); /////??????
- string Cookie = "exesubmitlang=2; PHPSESSID=8qdqoujc8ptncdb518cksqr687; CNZZDATA1254072405=385429082-1445151305-http%253A%252F%252Facm.hdu.edu.cn%252F%7C1446089001";
- 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;
- if (SOCKET_ERROR == send(sock, reqInfo.c_str(), reqInfo.size(), 0))
- {
- cout << "send error! 错误码: " << WSAGetLastError() << endl;
- closesocket(sock);
- }
(五)解析提交后返回的State页面,提取最终的结果(是否Accepted)、耗时和空间占用
这个就比较简单了,数据分析,可能实现都不一样,我是先定位的题号:
plain copy
- void GetResult(string &allHtml,int Prob) //解析出state.php中的结果,空间,时间
- {
- StateAns="";
- StateSapce="";
- StateTime="";
- char d[200];
- _itoa(ProblemID, d, 10);
- strcat(d, "</a>");
- int pos = allHtml.find((string)d);
- int Mpos = pos;
- int Tpos;
- if (Mpos == string::npos)
- return;
- else
- {
- Mpos += 17;
- while (1)
- {
- if (allHtml[Mpos] == '<')
- {
- Tpos = Mpos;
- break;
- }
- StateSapce += allHtml[Mpos];
- Mpos++;
- }
- cout << "使用的空间大小为:" << StateSapce << endl;
- }
- Tpos += 9;
- while (1)
- {
- if (allHtml[Tpos] == '<')
- break;
- StateTime += allHtml[Tpos];
- Tpos++;
- }
- cout << "使用的时间为:" << StateTime << endl;
- if (pos == string::npos)
- return;
- else
- {
- pos = pos - 52;
- int begin;
- while (1)
- {
- if (allHtml[pos] == '>')
- {
- begin = pos;
- break;
- }
- pos--;
- }
- for (int i = begin + 1;allHtml[i]!='<';i++)
- {
- StateAns += allHtml[i];
- }
- }
- cout << "最终的state界面的答案是:" << "---------------::::::" << StateAns << endl;
- }
(六)将刷题过程存储至SQL Server数据库,供以后的数据分析
要点就是C++使用ado连接SQL Server数据库
plain copy
- CoInitialize(NULL);//初始化Com库
- _ConnectionPtr pMyConnect = NULL;//这是个对象指针,关于对象指针的内容可以百度一下,不过不理解也就算了
- HRESULT hr = pMyConnect.CreateInstance(__uuidof(Connection));
- //将对象指针实例化
- if (FAILED(hr))
- {
- cout << "_ConnectionPtr对象指针实例化失败!" << endl;
- return 0;
- }
- _bstr_t strConnect="Driver={sql server};server=Tach-PC\\SQLEXPRESS;uid=tach1;pwd=123456;database=ProblemSolved"; //SQLSERVER
- //这是连接到SQL SERVER数据库的连接字符串,其中的参数要自己改
- try{
- pMyConnect->Open(strConnect, "", "", NULL);
- }//连接到数据库,要捕捉异常
- catch (_com_error &e){
- cout << "连接数据库异常!" << endl;
- cout << e.ErrorMessage() << endl;
- }
注意将上面的server名字,uid和pwd改为你自己的。
下图的Queuing请无视,因为我为了速度并没有Sleep(),页面还没有显示出结果,对,我比较懒==
好啦,到这里就大功告成啦!别刷太快哦,貌似被hdu封了一次IP。。。。。(囧)
最后项目的完整源码在我的Github,欢迎大家fork!