前言:
在寫這篇文章之前,首先感謝給我思路以及幫助過我的學長們
當初抱着想試試的想法,用過他們的程式,嗯~ o(* ̄▽ ̄*)o。
1、比如那個用C++寫AC機的學長,他的程式是用VS2013編譯的,原諒我的C++并不是學的很好,照着他的教程試過好多次也沒有運作成功,最終隻能以失敗告終了!
不過源程式我一直保留着。
2、正在入門C#的我看到這個程式,果斷fork,或許會看不懂,不過……總會有用的!喏!這是一個模拟人操作的程式,模拟複制,模拟滑鼠點選送出,缺點就是,如果程式是背景運作便會停止~( ╯□╰ )……表示并不是自己想要的那一種……
3、部落客還是用C#寫的,50%的AC率看起來好”*/¥#“,隻是沒有開源,貌似自己在YTU OJ上的AC率隻有56%。
4、用 node 來模拟使用者的這個過程,其實就是一個 模拟登入+模拟送出 的過程,根據經驗,模拟送出這個 post 過程肯定會帶有 cookie。送出的 code 哪裡來呢?直接爬取搜尋引擎就好了。整個思路非常清晰:模拟登入(post),從搜尋引擎爬取 code(get),模拟送出(post)。那這個是不是看起來很不錯呢?對,隻是Node普通環境我通過baidu找教程配置好了,然而還是不能運作,估計還缺少一個什麼環境吧!隻有一百多行代碼寫出的AC機果然不容小觑,運作不了……【笑哭】【笑哭】【笑哭】
正文
至于網上找的,終究不如自己寫的,那麼,先上圖!
第12名的千千是我哦!
請忽略我的正确率,相比學長做的那些,還是差了很多……不過,先給各位辛苦刷題的ACMer賠個不是,畢竟這是很投機的一種方式,僅供娛樂,還請各位見諒!
下面我們一步一步開始AUTO AC之旅吧!
1、同樣使用socket程式設計模拟HTTP協定GET請求向伺服器發送頁面請求
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);
}
我們使用Socket程式設計通過bind(),connect(),send(),recv()這些函數建立與伺服器的連接配接。 接下來我們想:點選按鈕的過程發生了什麼,我們使用send()需要将什麼資訊發送至伺服器,這裡就要涉及到HTTP協定的GET請求。
2、借助搜尋引擎擷取csdn部落格連結
先上幾張圖~
看到這些不同的搜尋引擎所搜尋的結果,你有什麼發現麼?
沒錯,注意左下角哦!百度和360的搜尋引擎都對連結做了加密處理,對于這樣的連結,我們從源代碼中是很難提取對應頁面的原始位址的!是以最終找到了必應搜尋引擎(有道搜尋也可以),不過,缺點就是這些對于我們來說不太常用的搜尋引擎對我們想要的頁面的索引量也不是很多。不過,夠用了~
有沒有人想問我為什麼不用CSDN内置的搜尋呢?我隻能說,CSDN的搜尋引擎還沒有我自己寫的find函數好用
,有時候站内搜尋已有的文章也搜尋不到!
void GetCSDNurl(string &allHtml) ///提取網頁中的csdn部落格網址
{
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;
}
}
曾經用PHP做<微信公衆平台:imqxms> 的背景的時候用過正規表達式,結果發現C++中也有。是以直接找學長的源碼為我所用啦!
(說出名稱隻求你的關注:imqxms)
3、從HTML源代碼中提取程式代碼
這張圖檔是網頁源代碼中的程式代碼部分!
明顯的特征就是<pre></pre>與我們常用的#include<>
,可以看到,'<'與'>'等一些字元都被改成了其他字元,這或許是網絡标記語言中的轉義字元吧!
void GetCode(string &allHtml) //提取代碼部分
{
CodeHtml = "";
int pos = allHtml.find("#include");
if (pos != string::npos)
{
for (int i = pos; i < (int)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'))return;
else if (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;
}
}
我們需要的是原來完整的代碼,那麼,接下來的任務就是将這段程式中所有的HTML轉義字元轉換成C++或者其他語言中的字元咯!
另外講一下幾個關于網頁表單很重要的編碼。
一個是url編碼,因為如果要傳送的資料中包含一些符号,可能會對原本的文本的分隔符産生沖突,比如& / =等一些用于位址的符号,是以需要把這些符号轉義
網頁中都是利用url編碼進行轉義的。
分gb2312和utf-8兩種
gb2312是中文編碼,utf-8是一種更普遍的面向更多語言的編碼,兩種編碼在字母的ASCII碼上,是一樣的
但是在中文漢字上,gb2312是占2個位元組,utf-8占3個位元組,這也會導緻兩種編碼下的内容url編碼之後會不一樣
是以我們還需要做一個對不同編碼進行轉換的函數!
char* U2G(const char* utf8) ///UTF-8 to GB2312
{
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;
}
char* G2U(const char* gb2312) ///GB2312 TO UTF-8
{
int len = MultiByteToWideChar(CP_ACP, 0, gb2312, -1, NULL, 0);
wchar_t* wstr = new wchar_t[len + 1];
memset(wstr, 0, len + 1);
MultiByteToWideChar(CP_ACP, 0, gb2312, -1, wstr, len);
len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
char* str = new char[len + 1];
memset(str, 0, len + 1);
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL);
if (wstr) delete[] wstr;
return str;
}
喏,就是這個,百度(Bing)就是好用
string HTMLTOC(string &CodeHtml) ///HTML轉義字元轉義處理
{
string ans;
for (int i = 0; i < (int)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') /// /n; \\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;
}
好詫異為什麼有的學長寫的代碼中沒有這一部分,難道說……[撇嘴],,其實,有一個問題,上面的代碼實作了字元的轉換,可是這種算法和KMP模式比對哪種更好呢?
我不會告訴你我昨晚C語言結課考試的時候也有這麼一道題(把句子中所有的don't 替換成do not),用的也是上面的方法!
---------------------------貌似已經說了,( ╯□╰ )……
4、利用網頁中的cookie來模拟線上
因為每一個賬号登入之後都會有唯一的一個cookie,那種模拟滑鼠點選的程式,實質上是一個浏覽器,可是我在控制台下不會做到這些
,是以做出來的AC機隻能在你用浏覽器打開的情況下,并且你的賬号線上的情況下才能運作
string Cookie = "exesubmitlang=2; PHPSESSID=" + PHPSESSID + "; CNZZDATA1254072405=" + CNZZDATA;
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;
說到Cookice,想要檢視你在浏覽器中的Cookice資訊,以谷歌浏覽器為例,滑鼠右鍵>>檢查裡面有驚喜哦!
用來刷題的是我的小号
5、既然能做到從網頁中提取一個程式的源代碼,那麼,我也可以在HDU OJ的狀态欄找到我們送出之後的最終結果咯!
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 (true)
{
if (allHtml[Mpos] == '<')
{
Tpos = Mpos;
break;
}
StateSapce += allHtml[Mpos];
Mpos++;
}
cout << "空間: " << StateSapce << endl;
}
Tpos += 9;
while (true)
{
if (allHtml[Tpos] == '<')break;
StateTime += allHtml[Tpos];
Tpos++;
}
cout << "耗時: " << StateTime << endl;
if (pos == string::npos)return;
else
{
pos = pos - 52;
int begin;
while (true)
{
if (allHtml[pos] == '>')
{
begin = pos;
break;
}
pos--;
}
for (int i = begin + 1; allHtml[i] != '<'; i++)StateAns += allHtml[i];
}
cout << "結果: " << "---------------::::::" << StateAns << endl;
}
if(StateAns=="Accepted
")break;
//如果AC,跳出本次循環,也就是執行下一道題目!
6、别忘了在你的循環後面加上Sleep函數哦!
不然那個狀态頁面整個頁面都會是你的送出。
貌似我曾經因為沒有Sleep,然後被不認識的人加了好友【撇嘴】,并不知道他是怎麼加到我的!然後告訴我看到我在航電上面刷題了,是以問了我幾道題
有的人或許會問,HDU上面不是有1000-5674個題目嗎?為什麼你總共才AC了兩千多道題目,對于這個問題,隻能怪我算法沒有好好優化,或許可以去ACM之家找源碼哦!
7、可執行檔案在我的GitHub裡面,歡迎大家Fork,别刷太快哦!不然會超過我的。。。【祈求】【祈求】【祈求】
至于源碼,以後放出吧! 已放出 千千好委屈,但千千不說!
後記
每一個ACMer都應該有自己的部落格,那麼就這個啦!
<a href="www.dreamwings.cn">若是涼夜已成夢</a>