原文地址:http://www.cnblogs.com/wurang/archive/2013/06/14/3119023.html

【前言】

从CSDN转投cnBlog也有一段时间了,发现cnBlog中也有类似CSDN的迷你博客的功能,就是闪存。闪存使用了幸运星的机制也引发一大批人没事就来刷星星……虽然不知道有什么用,但无聊中也试过几次。由于幸运星随机分发,那么就有一个想法,不停的发消息,不是星星的就删掉以免有刷屏嫌疑。手动操作起来当然怪麻烦的,于是干脆用代码,这就产生了一个需求:获取html,解析,自动提交登陆,自动发布,判断是否是星星,删除等等。

【方案】Webbrowser

因为只是想随手玩下,没考虑复杂性和完善程度,我最先想到的是用webbrowser,然后获取html,纯手动解析。

  Step1:表单填充

首先当然是放置一个Webbrowser控件,为了方便,直接设置了url为http://passport.cnblogs.com/login.aspx

然后登陆http://passport.cnblogs.com/login.aspx,查看源代码获取登陆框的id。

<input name="tbUserName" type="text" id="tbUserName" class="Textbox" />

<input name="tbPassword" type="password" id="tbPassword" class="Textbox" />

<input type="submit" name="btnLogin" value="登  录" onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("btnLogin", "", true, "", "", false, false))" id="btnLogin" class="Button" style="margin-top: 8px" />

 程序开始运行后Webbrowser会自动打开http://passport.cnblogs.com/login.aspx,我们就要在Webbrowser加载页面结束后来做表单填充,那么如何得知页面已经被加载完成了呢?这里可以使用Webbrowser的DocumentCompleted事件,当Webbrowser加载页面结束后,会触发这个事件,我们只需要在这个事件中做表单填充就可以了。表单填充和提交的方法如下:

            HtmlDocument doc = wbBlog.Document;
foreach (HtmlElement em in doc.All)
{
string str = em.Name; switch (str)
{
case "tbUserName":
em.SetAttribute("value", user);
break;
case "tbPassword":
em.SetAttribute("value", pwd);
break;
case "btnLogin":
isLogIn = true;
em.InvokeMember("click");
break;
}
}

如果登陆成功,页面应该跳转至主页,程序也需要导航到闪存的网址http://home.cnblogs.com/ing,如果未成功则还是停留在该页面,所以通过判断webbrowser的当前url就可以知道是否登陆成功了,这是一个取巧的方法。当然,判断当前url也需要在DocumentCompleted事件中,因为我们需要等待页面刷新结束后才能做判断。

            isLogIn = false;
if (wbBlog.Url.ToString() == "http://passport.cnblogs.com/login.aspx")
{
System.Windows.MessageBox.Show("用户名或密码错误!");
return;
}
else
{
isSetForm = false;
mylogin.Close();
wbBlog.Navigate("http://home.cnblogs.com/ing/");
this.Show();
}

这时候可能会发现DocumentCompleted事件中需要做的事有点多了,会不会有冲突或者重复执行?所以我们需要一些标记来控制。在上面的代码中可以看到isLogIn这个变量,就是用于控制在DocumentCompleted到底要执行判断还是执行表格填充。

 Step2:发布闪存

       登陆成功后,webbrowser跳转到闪存页面,这时候需要程序自动发布闪存,原理也是表单填充和提交。可以看下页面的源码。

<textarea class="ing_text" onblur="IngIsEmpty();" onfocus="HideTip()" onkeydown="return PublicIngEnterNew(event)" id="txt_ing">你在做什么?你在想什么?</textarea>

 <input type="submit" name="btnLogin" value="登  录" onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(&quot;btnLogin&quot;, &quot;&quot;, true, &quot;&quot;, &quot;&quot;, false, false))" id="btnLogin" class="Button" style="margin-top: 8px" />    

然后程序需要做的就是不停的做填充和提交,然后判断是否有星星,如果有就退出循环。

            HtmlDocument doc = wbBlog.Document;
foreach (HtmlElement em in doc.All)
{
string str = em.Id; switch (str)
{
case "txt_ing":
content = em;
em.SetAttribute("value", txtContent.Text);
break;
case "btn_ing_publish":
isPublish = true;
submit = em;
em.InvokeMember("click");
break;
}
}

提交表单后,页面会刷新,所以判断是否有星星也是要在DocumentCompleted事件中,同时需要isPulish这个标记来表示是否需要执行判断方法。

       Step3:判断是否有星

       分析闪存页面的源码可以看到每一条闪存的html都是下面这样:

<div class="feed_body" id="feed_content_414865"><a href="/u/516258/" class="ing-author" target="_blank">作者</a>: <span class="ing_body" id="ing_body_414865">内容</span><img src="http://static.cnblogs.com/images/ing_lucky.png" class="ing_icon_lucky" alt="" title="这是幸运闪"/> <a class="ing_time" href="/ing/414865/" title="发布于 6-5 10:15:43,点击进入详细页面" target="_blank">31分钟前</a> <a href="#" id="a_414865" onclick="showCommentBox(414865,516258);return false;" class="ing_reply" title="点击进行回应">回应</a><div class="ing_comments"><div class='feed_ing_comment_block'><ul id="comment_block_414865"><li style="display:none">&nbsp;</li></ul><div class='ing_cm_box' id='panel_414865'></div></div></div></div><div class="clear"></div></div></li><li class="entry_a"><div class="ing-item"><div class="feed_avatar"><a href="/u/liujinyao/" target="_blank"><img width="36" height="36" src="http://pic.cnitblog.com/face/502329/20130312132011.png" alt=""/></a></div>

所以首先要获取id格式是feed_content_***的所有div,然后判断这个htmlelement中是否包含了自己发布的信息,如果是就锁定这个element,然后判断是否包含

<img src="http://static.cnblogs.com/images/ing_lucky.png" class="ing_icon_lucky" alt="" title="这是幸运闪"/>

如果有,则提示发布成功,如果没有则删除这条闪存并继续发布。需要注意的是,发布一条新的闪存后并没有删除选项,

需要刷新一下页面才会看到,包括查看是否有星星也是要刷新后才能判断。

             HtmlDocument doc = wbBlog.Document;
foreach (HtmlElement em in doc.All)
{
string str = em.Id;
if (str != null && str.Contains("feed_content") && em.OuterHtml.Contains(txtContent.Text))
{
if (em.OuterHtml.Contains("http://static.cnblogs.com/images/ing_lucky.png"))
{
lstInfo.Items.Add("获得幸运闪:" + txtContent.Text);
}
else
{
//删除
}
}
}

       Step4:删除闪存

程序写到这里我遇到了麻烦,由于我是获取id是feed_content_***的div,现在要取得div中的删除链接,发现这个a链接没有id,那该如何获取?貌似需要用正则表达式了。但这里偷了个懒,获取页面所有的a连接,然后判断title属性是不是为“删除这个闪存”,从而获取这个a连接元素。

<a class='recycle' onclick='return DelIng(415025)' href='javascript:void(0);' title='删除这个闪存' >

得到a连接的元素之后就可以操作它的Click事件了,但又有一个新问题,点击删除之后,这货居然弹出一个Confirm对话框,继而引出一个老问题,如何干掉网页弹出的Confirm和Alert对话框。这里使用一个原始方法,让页面所有的function confirm()都自动返回ture。首先需要引用Microsoft.mshtml和Interop.SHDocVw,具体操作代码如下:

                                HtmlElementCollection hrefs = em.GetElementsByTagName("a");
foreach (HtmlElement h in hrefs)
{
if (h.GetAttribute("title") == "删除这个闪存")
{
IHTMLDocument2 doc1 = (wbBlog.ActiveXInstance as SHDocVw.WebBrowser).Document as IHTMLDocument2;
doc1.parentWindow.execScript("function confirm(){return true;}", "javascript");
h.InvokeMember("click");
//等待
return;
}
}

做到这一步,基本功能已经实现,现在需要做的就是在发布,判断和删除这几个操作中做循环,需要注意的是网页页面上的刷新和删除闪存是通过ajax刷新部分div,所以webbrowser不会触发DocumentCompleted事件,这里可以仿照winform写一个DoEvent,还需要Sleep一段时间。然后才能读取刷新后的页面信息。

        public void DoEvent()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
}
public object ExitFrame(object f)
{
((DispatcherFrame)f).Continue = false;
return null;
}

【结束】

到这里程序就写完了,运行一下,发现程序在不停的控制发布和删除,但还是有问题,刷不到星星。cnblog的星星虽说是随机分配,但是相同的内容,或者相隔时间太短都会被排除,闪存还有两个机制,同一页面只允许一个用户发布五条信息,用户每天发布闪存的数量是有上限的,具体多少没有统计,是用程序刷了几百条后给出的提示。所以程序虽然写完了,但却没有达到最初的效果,这不禁让人失望。不过换一种思路,一次发布五条不同的闪存(需前后有数秒间隔),然后依次判断是否有星星,删除没有星星的,保留有星星的,这样应该就符合规则了。当然,本来是随手写写的东西发现还是挺复杂的,这部分就没有再实现了。其实这篇文章的主要目的是HTML解析不是么?

回过头来想想,如果真的要做这样一个工具,用webbrowser做html解析会导致程序的可维护性和执行效率很低,如果是解析html,推荐使用Html Agility Pack,而提交删除等操作则可以用网页开发工具抓个包分析然后用ajax直接发送请求。举个栗子,在之前的代码中,我们要获取闪存的div以及闪存的内容并判断是否有星星等操作是比较复杂的,如果使用Html Agility Pack,Xpath将轻松搞定一切。

            string pageUrl = "http://home.cnblogs.com/ing/";
WebClient wc = new WebClient();
byte[] pageSourceBytes = wc.DownloadData(new Uri(pageUrl));
string pageSource = Encoding.GetEncoding("utf-8").GetString(pageSourceBytes); HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(pageSource); string xpath = @"//div[@class='feed_body']";
HtmlNodeCollection keyNodes = doc.DocumentNode.SelectNodes(xpath);
foreach (HtmlNode node in keyNodes)
{
HtmlNode img = node.SelectSingleNode("./img[@class='ing_icon_lucky']"); if (img != null)
{
Debug.WriteLine(node.InnerText);
// Debug.WriteLine("luck: " + keyNode.SelectSingleNode("//span[@class='ing_body']").InnerText + "\n");
}
}

最后附上程序源码,有兴趣的可以重构一下程序,完成未实现的功能部分。

源码下载

最新文章

  1. 【系统篇】从C/C++语言到进程启动背后的故事
  2. Nutch主要类代码分析之一(Injector)
  3. FSG报表定义导入
  4. 早安Visual Studio!一次重构之旅,夏洛特烦恼
  5. (12)nehe教程6 纹理映射
  6. Cocos2d-x CCEditBox &amp; CCTextFieldTTF
  7. 【使用Itext处理PDF文档(新建PDF文件、修改PDF文件、PDF中插入图片、将PDF文件转换为图片)】
  8. 【数论线性筛】洛谷P1865 A%B problem
  9. EBS业务学习之采购管理
  10. 怎么使用zepto.js的tap事件引起的探索
  11. luogu P3721 [AH2017/HNOI2017]单旋
  12. LeetCode(75):分类颜色
  13. Linux下java开发环境配置总结
  14. Bugku-CTF之你必须让他停下+头等舱
  15. rand_1tom 产生 rand_1ton
  16. CSS/让一个盒子消失的5中方法
  17. Linux登录超时自动退出处理办法
  18. transiton,transform,animation,border-image
  19. 重识linux-linux系统服务相关
  20. 如何使用SetTimer

热门文章

  1. bzoj 2159 Crash 的文明世界 &amp;&amp; hdu 4625 JZPTREE ——第二类斯特林数+树形DP
  2. Hbase 参数配置及优化
  3. 插耳机对orientation sensor的影响
  4. laravel 网站速率优化
  5. python开发模块基础:re正则
  6. Docker - Upgrade from 1.12 to 1.13
  7. maven学习0 常用命令学习
  8. 如何规避javascript多人开发函数重名问题
  9. Wireshark捕获非加密的数据包
  10. ansibel---tag模块