手把手教你用C++ 写ACM自动刷题神器(冲入HDU首页)
2015-10-29 22:15
676 查看
少年,作为苦练ACM,通宵刷题的你 是不是想着有一天能够荣登各大OJ榜首,俯瞰芸芸众生,唔....要做到这件事情可是需要一定天赋的哦!
博主本身也搞过一段时间的acm,对刷题深有感触,不信可以去看我博客的acm题解(哈哈)。
不过,先给各位辛苦刷题的ACMer赔个不是,毕竟这是很投机的一种方式,仅供娱乐,还请各位见谅!
受学长的启蒙,打算自己做一个使用C++语言完成的自动刷题神器,也可以叫自动AC机(什么?ac自动机...吓尿),先来看一下成果:
(注:这是第一次刷完后的排名,后来对代码进行了很大的优化,但是由于时间关系,没有再刷一遍,否则肯定进入前十!)
![](https://img-blog.csdn.net/20151029211916114?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
第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
一开始的时候我想利用百度搜索引擎,但是发现返回到HTML页面中根本无法找出csdn博客的链接特征,后来发现,原来是baidu为了避免爬虫爬取进行了加密处理,如下图:
![](https://img-blog.csdn.net/20151029215031218?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
注意左下角是第一个csdn博客的连接....坑了我一段时间。
后来发现360搜索没有加密,所以那就用360吧。。提取出来放入vector保存,然后使用C++11的正则表达式解析出csdn博客的地址。
(三)从HTML中解析出代码
![](https://img-blog.csdn.net/20151029215351615?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
注意代码一般由#include开始,结束的位置是</textarea>或者</pre>,我们利用这个特征进行提取。
(四)对代码进行编码转换的处理,模仿HTTP协议的POST请求向服务器提交代码
我们可以看到上面的代码并不是解析出来就能用的,还包含有<,>等HTML的元素,并且还有汉字转码的问题需要我们需要处理。POST的时候,还需要考虑HTTP编码,
将空格回车等转换为十六进制发送提交,不说了,直接看代码:
POST:注意头信息要全,并且Cookie要写你自己的,一旦浏览器关闭就会失效,要重置:
(五)解析提交后返回的State页面,提取最终的结果(是否Accepted)、耗时和空间占用
这个就比较简单了,数据分析,可能实现都不一样,我是先定位的题号:
(六)将刷题过程存储至SQL Server数据库,供以后的数据分析
要点就是C++使用ado连接SQL Server数据库
下图的Queuing请无视,因为我为了速度并没有Sleep(),页面还没有显示出结果,对,我比较懒==
![](https://img-blog.csdn.net/20151029220754921?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
好啦,到这里就大功告成啦!别刷太快哦,貌似被hdu封了一次IP。。。。。(囧)
最后项目的完整源码在我的Github,欢迎大家fork!
有疑问或者优化请留言或者 通过邮箱联系我,联系方式在博客的左上角,希望给大家学习网络编程带来帮助!
博主本身也搞过一段时间的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
//向服务器发送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博客的地址。
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>,我们利用这个特征进行提取。
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编码,
将空格回车等转换为十六进制发送提交,不说了,直接看代码:
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;
//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要写你自己的,一旦浏览器关闭就会失效,要重置:
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)、耗时和空间占用
这个就比较简单了,数据分析,可能实现都不一样,我是先定位的题号:
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数据库
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!
有疑问或者优化请留言或者 通过邮箱联系我,联系方式在博客的左上角,希望给大家学习网络编程带来帮助!
相关文章推荐
- c++: size_type与 size_t一些概念
- 深入理解C语言的函数调用过程
- 【LeetCode从零单刷】Symmetric Tree
- 学习C++,应该循序渐进看哪些书?
- C++ main函数 argc,argv传递参数的含义
- C语言学习-通过柴田望洋的《明解C语言》(3)
- 《C++并发编程实战》读书笔记3---线程同步
- stm32 mdk c++(error: #29: expected an expression)
- 黑马程序员-------C语言回顾-运算符和循环
- c++为你做了什么(1)构造 析构 拷贝 赋值
- C++_的引用的析构,引用空间的释放
- [C++] const and char*
- ——黑马程序员——C语言中程序结构(二)
- c语言中认识指针
- 使用class或struct关键字定义类
- C语言第十二天:通讯录练习(C语言最后一天老师带我们做的Project)
- 《Essential C++》读书笔记
- Function语意学 Member的各种调用方式
- C++plus 4.13
- 1014--C语言文法定义