您的位置:首页 > 编程语言 > C语言/C++

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

2015-10-29 22:15 676 查看
少年,作为苦练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 

//向服务器发送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!

有疑问或者优化请留言或者 通过邮箱联系我,联系方式在博客的左上角,希望给大家学习网络编程带来帮助!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: