自制自动AC机器人日解千题攻占HDOJ

#前言

不说话,先猛戳 Ranklist 看我排名。

这是用 node 自动刷题大概半天的 “战绩”,本文就来为大家简单讲解下如何用 node 做一个 “自动AC机”。

#过程

先来扯扯 oj(online judge)。计算机学院的同学应该对 ACM 都不会陌生,ACM 竞赛是拼算法以及数据结构的比赛,而 oj 正是练习 ACM 的 “场地”。国内比较有名的 oj 有 poj、zoj 以及 hdoj 等等,这里我选了 hdoj (完全是因为本地上 hdoj 网速快)。

在 oj 做题非常简单。以 hdoj 为例,先注册个账号(http://bestcoder.hdu.edu.cn/register.php),然后随便打开一道题(http://acm.hdu.edu.cn/showproblem.php?pid=1000),点击最下面的 Submit 按钮(http://acm.hdu.edu.cn/submit.php?pid=1000),选择提交语言(Language),将答案复制进去,最后再点击 Submit 按钮提交,之后就可以去查看是否 AC(Accepted) 了(http://acm.hdu.edu.cn/status.php)。

用 node 来模拟用户的这个过程,其实就是一个 模拟登录+模拟提交 的过程,根据经验,模拟提交这个 post 过程肯定会带有 cookie。提交的 code 哪里来呢?直接爬取搜索引擎就好了。

整个思路非常清晰:

  1. 模拟登录(post)
  2. 从搜索引擎爬取 code(get)
  3. 模拟提交(post)

#模拟登录

首先来看模拟登录,根据经验,这大概是一个 post 过程,会将用户名以及密码以 post 的方式传给服务器。打开 chrome,F12,抓下这个包,有必要时可以将 Preserve log 这个选项勾上。

请求头居然还带有 Cookie,经测试,key 为 PHPSESSID 的这个 Cookie 是请求所必须的,这个 Cookie 哪来的呢?其实你只要一打开 http://acm.hdu.edu.cn/ 域名下的任意地址,服务端便会把这个 Cookie “种” 在浏览器中。一般你登录总得先打开登录页面吧?打开后自然就有这个 Cookie 了,而登录请求便会携带这个 Cookie。一旦请求成功,服务器便会和客户端建立一个 session,服务端表示这个 cookie 我认识了,每次带着这个 cookie 请求的我都可以通过了。一旦用户退出,那么该 session 中止,服务端把该 cookie 从认识名单中删除,即使再次带着该 cookie 提交,服务端也会表示 “不认识你了”。

所以模拟登录可以分为两个过程,首先请求 http://acm.hdu.edu.cn/ 域名下的任意一个地址,并且将返回头中 key 为 PHPSESSID 的 Cookie 取出来保存(key=value 形式),然后携带 Cookie 进行 post 请求进行登录。

// 模拟登录
function login() {
  superagent
    // get 请求任意 acm.hdu.edu.cn 域名下的一个 url
    // 获取 key 为 PHPSESSID 这个 Cookie
    .get('http://acm.hdu.edu.cn/status.php')
    .end(function(err, sres) {
      // 提取 Cookie
      var str = sres.header['set-cookie'][0];
      // 过滤 path
      var pos = str.indexOf(';');

      // 全局变量存储 Cookie,登录 以及 post 代码时候用
      globalCookie = str.substr(0, pos);

      // 模拟登录
      superagent
        // 登录 url
        .post('http://acm.hdu.edu.cn/userloginex.php?action=login')
        // post 用户名 & 密码
        .send({"username": "hanzichi"})
        .send({"userpass": "hanzichi"})
        // 这个请求头是必须的
        .set("Content-Type", "application/x-www-form-urlencoded")
        // 请求携带 Cookie
        .set("Cookie", globalCookie)
        .end(function(err, sres) {
          // 登录完成后,启动程序
          start();
        });
    });
}

模拟 HTTP 请求的时候,有些请求头是必须的,有些则是可以忽略。比如模拟登录 post 时,Content-Type 这个请求头是必须携带的,找了我好久,如果程序一直启动不了,可以试试把所有请求头都带上,逐个进行排查。

#搜索引擎爬取 Code

这一部分我做的比较粗糙,这也是我的爬虫 AC 正确率比较低下的原因。

我选择了百度来爬取答案。以 hdu1004 这道题为例,如果要搜索该题的 AC 代码,我们一般会在百度搜索框中输入 hdu1004,而结果展现的页面 url 为 https://www.baidu.com/s?ie=UTF-8&wd=hdu1004。这个 url 还是非常有规律的,https://www.baidu.com/s?ie=UTF-8&wd= 加上 keyword。

百度的一个页面会展现 10 个搜索结果,代码里我选择了 ACMer 在 csdn 里的题解,因为 csdn 里的代码块真是太好找了,不信请看。

csdn 把代码完全放在了一个 class 为 cpp 的 dom 元素中,简直是太友好了有没有!相比之下,博客园等其他地方还要字符串过滤,为了简单省事,就直接选取了 csdn 的题解代码。

一开始我以为,一个搜索结果页有十条结果,每条结果很显然都有一个详情页的 url,判断一下 url 中有没有 csdn 的字样,如果有,则进入详情页去抓 code。但是百度居然给这个 url 加密了!

我注意到每个搜索结果还带有一个小字样的 url,没有加密,见下图。

于是我决定分析这个 url,如果带有 csdn 字样,则跳转到该搜索结果的详情页进行代码抓取。事实上,带有 csdn 的也不一定能抓到 code( csdn 的其他二级域名,比如下载频道 http://download.csdn.net/),所以在 getCode() 函数中写了个 try{}…catch(){} 以免代码出错。

// 模拟百度搜索题解
function bdSearch(problemId) {
  var searchUrl = 'https://www.baidu.com/s?ie=UTF-8&wd=hdu' + problemId;
  // 模拟百度搜索
  superagent
    .get(searchUrl)
    // 必带的请求头
    .set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36")
    .end(function(err, sres) {
      var $ = cheerio.load(sres.text);
      var lis = $('.t a');
      for (var i = 0; i < 10; i++) {
        var node = lis.eq(i);

        // 获取那个小的 url 地址
        var text = node.parent().next().next().children("a").text();

        // 如果 url 不带有 csdn 字样,则返回
        if (text.toLowerCase().indexOf("csdn") === -1)
          continue;

        // 题解详情页 url
        var solutionUrl = node.attr('href');
        getCode(solutionUrl, problemId);
      }
    });
}

bdSearch() 函数传入一个参数,为 hdoj 题目编号。然后去爬取百度获取题解详情页的 url,经过测试 爬取百度必须带有 UA!其他的就非常简单了,代码里的注释很清楚。

// 从 csdn 题解详情页获取代码
function getCode(solutionUrl, problemId) {

  superagent.get(solutionUrl, function(err, sres) {
    // 为防止该 solutionUrl 可能不是题解详情页
    // 没有 class 为 cpp 的 dom 元素
    try {
      var $ = cheerio.load(sres.text);

      var code = $('.cpp').eq(0).text();

      if (!code)
        return;
      
      post(code, problemId);
    } catch(e) {

    }
    
  });
}

getCode() 函数根据题解详情页获取代码。前面说了,csdn 的代码块非常直接,都在一个类名为 cpp 的 dom 元素中。

#模拟提交

最后一步来看模拟提交。我们可以抓一下这个 post 包看看长啥样。

很显然,Cookie 是必须的,我们在第一步模拟登录的时候已经得到这个 Cookie 了。因为这是一个 form 表单的提交,所以 Content-Type 这个请求 key 也需要携带。其他的话,就在请求数据中了,problemid 很显然是题号,code 很显然就是上面求得的代码。

// 模拟代码提交
function post(code, problemId) {
  superagent
    .post('http://acm.hdu.edu.cn/submit.php?action=submit')
    .set('Content-Type', 'application/x-www-form-urlencoded')
    .set("Cookie", globalCookie)
    .send({"problemid": problemId})
    .send({"usercode": code})
    .end(function (err, sres) {
    });
}

#完整代码

完整代码可以参考 Github

其中 singleSubmit.js 为单一题目提交,实例代码为 hdu1004 的提交,而 allSubmit.js 为所有代码的提交,代码中我设置了一个 10s 的延迟,即每 10s 去百度搜索一次题解,因为要爬取 baidu、csdn 以及 hdoj 三个网站,任意一个网站 ip 被封都会停止整个灌水机的运作,所以压力还是很大的,设置个 10s 的延迟后应该木有什么问题了。

学习 node 主要就是因为对爬虫有兴趣,也陆陆续续完成了几次简单的爬取,可以移步我的博客中的 Node.js 系列。这之前我把代码都随手扔在了 Github 中,居然有人 star 和 fork,让我受宠若惊,决定给我的爬虫项目单独建个新的目录,记录学习 node 的过程,项目地址 https://github.com/hanzichi/funny-node。我会把我的 node 爬虫代码都同步在这里,同时会记录每次爬虫的实现过程,保存为每个小目录的 README.md 文件。

#后续优化

仔细看,其实我的爬虫非常 “智弱”,正确率十分低下,甚至不能 AC hdu1001!我认为可以从以下几个方面进行后续改进:

  • 爬取 csdn 题解详情页时进行 title 过滤。比如爬取 hdu5300 的题解 https://www.baidu.com/s?ie=UTF-8&wd=hdu5300,搜索结果中有 HDU4389,程序显然没有预料到这一点,而会将之代码提交,显然会 WA 掉。而如果在详情页中进行 title 过滤的话,能有效避免这一点,因为 ACMer 写题解时,title 一般都会带 hdu5300 或者 hdoj5300 字样。

  • 爬取具体网站。爬取百度显然不是明智之举,我的实际 AC 正确率在 50% 左右,我尼玛,难道题解上的代码一半都是错误的吗?可能某些提交选错了语言(post 时有个 language 参数,默认为 0 为 G++提交,程序都是以 G++ 进行提交),其实我们并不能判断百度搜索得到的题解代码是否真的正确。如何提高正确率?我们可以定向爬取一些题解网站,比如 http://accepted.com.cn/ 或者 http://www.acmerblog.com/,甚至可以爬取 http://acm.hust.edu.cn/vjudge/problem/status.action 中 AC 的代码!

  • 实时获取提交结果。我的代码写的比较粗糙,爬取百度搜索第一页的 csdn 题解代码,如果有 10 个就提交 10 个,如果没有那就不提交。一个更好的策略是实时获取提交结果,比如先提交第一个,获取返回结果,如果 WA 了则继续提交,如果 AC 了那就 break 掉。获取提交结果的话,暂时没有找到这个返回接口,可以从 http://acm.hdu.edu.cn/status.php 中进行判断,也可以抓取 user 详情页 http://acm.hdu.edu.cn/userstatus.php?user=hanzichi

PS:我在 hdoj 的账号用户名和密码均为 hanzichi,有兴趣的可以用我的账号继续刷题。

少年,作为苦练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.       
  7.     string::const_iterator start = allHtml.begin();  
  8.     string::const_iterator end = allHtml.end();  
  9.     while (regex_search(start, end, mat, pattern))  
  10.     {  
  11.         string msg(mat[1].first, mat[1].second);  
  12.         blogUrl.push_back(msg);  
  13.         start = mat[0].second;  
  14.     }  
  15. }  

(三)从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.               
  10.             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] == '>'))  
  11.             {  
  12.                 return ;  
  13.             }  
  14.             CodeHtml += allHtml[i];  
  15.         }  
  16.     }  
  17.     else  
  18.     {  
  19.         cout << "未找到合适的代码!" << endl;  
  20.         return;  
  21.     }  
  22.       
  23. }  

(四)对代码进行编码转换的处理,模仿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.   
  52. string ASCtoHex(int num) //十进制转换成十六进制  
  53. {  
  54.     char str[] = "0123456789ABCDEF";  
  55.     int temp=num;  
  56.     string ans;  
  57.     while (temp)  
  58.     {  
  59.         ans += str[temp % 16];  
  60.         temp /= 16;  
  61.     }  
  62.     ans += '%';  
  63.     reverse(ans.begin(), ans.end());  
  64.     return ans;  
  65. }  
  66. string GetRescode(string &CodeHtml)  
  67. {  
  68.     ResCode = "";  
  69.     for (int i = 0; i < CodeHtml.length(); i++)  
  70.     {  
  71.             //if (!isdigit(unsigned(CodeHtml[i])) && !isalpha(unsigned(CodeHtml[i])))  
  72.             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))  
  73.             {  
  74.                 //if (CodeHtml[i] == '\r' && (i + 1) < CodeHtml.length() && CodeHtml[i + 1] == '\n')  
  75.                 //if (CodeHtml[i] == '\r')  
  76.                 //{  
  77.                 //  ResCode += "++%0D";  
  78.                 //  
  79.                 //}  
  80.                 //else if (CodeHtml[i] == '\n')  
  81.                 if (CodeHtml[i] == '\n')  
  82.                 {  
  83.                     ResCode += "%0D%0A";  
  84.                 }  
  85.                 else if (CodeHtml[i] == '.' || CodeHtml[i] == '-' || CodeHtml[i] == '*')  
  86.                     ResCode += CodeHtml[i];  
  87.   
  88.                 /*if (CodeHtml[i] == 10) 
  89.                     ResCode += "%0D%0A"; 
  90.                     */  
  91.                 /*else if (CodeHtml[i] >= 0xB0 && (i + 1)<CodeHtml.length()&&CodeHtml[i + 1] >= 0xA1)//判断汉字 
  92.                 { 
  93.                 i++; 
  94.                 ResCode += CodeHtml[i]; 
  95.                 ResCode += CodeHtml[i + 1]; 
  96.                 }*/  
  97.                 else  
  98.                 {  
  99.                     string cur = ASCtoHex(CodeHtml[i]);  
  100.                     if (cur == "%9")  
  101.                         ResCode += "++++";  
  102.                     else if (cur == "%20")  
  103.                         ResCode += '+';  
  104.                     else if (cur == "%D")  
  105.                         ResCode += "++";  
  106.                     else  
  107.                         ResCode += cur;  
  108.                 }  
  109.   
  110.             }  
  111.             else  
  112.                 ResCode += CodeHtml[i];  
  113.   
  114.         }  
  115.           
  116.     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.   
  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=1003\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: zh-CN,zh;q=0.8";  
  6.     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=";  
  7.     ElseInfo = ElseInfo+ (string)s + "\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: zh-CN,zh;q=0.8";  
  8.     //向服务器发送POST请求   
  9.       
  10.     string HeaderP="check=0&problemid="+(string)s;  
  11.     HeaderP += "&language=2&usercode=";  
  12.       
  13.     ResCode = HeaderP + ResCode;  
  14.     char s[300];  
  15.     _itoa( ResCode.length(), s, 10);  /??????  
  16.     string Cookie = "exesubmitlang=2; PHPSESSID=8qdqoujc8ptncdb518cksqr687; CNZZDATA1254072405=385429082-1445151305-http%253A%252F%252Facm.hdu.edu.cn%252F%7C1446089001";  
  17.     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;  
  18.   
  19.     if (SOCKET_ERROR == send(sock, reqInfo.c_str(), reqInfo.size(), 0))  
  20.     {  
  21.         cout << "send error! 错误码: " << WSAGetLastError() << endl;  
  22.         closesocket(sock);  
  23.     }  

(五)解析提交后返回的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.     }  
  30.     Tpos += 9;  
  31.     while (1)  
  32.     {  
  33.         if (allHtml[Tpos] == '<')  
  34.             break;  
  35.         StateTime += allHtml[Tpos];  
  36.         Tpos++;  
  37.     }  
  38.     cout << "使用的时间为:" << StateTime << endl;  
  39.   
  40.     if (pos == string::npos)  
  41.         return;  
  42.     else  
  43.     {  
  44.         pos = pos - 52;  
  45.         int begin;  
  46.         while (1)  
  47.         {  
  48.             if (allHtml[pos] == '>')  
  49.             {  
  50.                 begin = pos;  
  51.                 break;  
  52.             }  
  53.             pos--;  
  54.         }  
  55.       
  56.         for (int i = begin + 1;allHtml[i]!='<';i++)  
  57.         {  
  58.             StateAns += allHtml[i];  
  59.         }  
  60.     }  
  61.     cout << "最终的state界面的答案是:" << "---------------::::::" << StateAns << endl;  
  62.   
  63.   
  64. }  

(六)将刷题过程存储至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!

有疑问或者优化请留言或者 通过邮箱联系我,联系方式在博客的左上角,希望给大家学习网络编程带来帮助!

#其他

其实 hdoj 排行榜第一页有不少都是机器人刷的。

比如 NKspider 这位仁兄,他是用 C++ 写的,具体可以参考 http://blog.csdn.net/nk_test/article/details/49497017

再比如 beautifulzzzz 这位哥们,是用 C# 刷的,具体可以参考 http://www.cnblogs.com/zjutlitao/p/4337775.html

同样用 C# 刷的还有 CSUSTrobot,过程可以参考 http://blog.csdn.net/qwb492859377/article/details/47448599

思路都是类似的,它们的代码都不短,node 才百来行就能搞定,实在是太强大了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值