(转)C# HTML解析示例---星星引发的血案
原文地址: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("btnLogin", "", true, "", "", 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"> </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");
}
}
最后附上程序源码,有兴趣的可以重构一下程序,完成未实现的功能部分。
(转)C# HTML解析示例---星星引发的血案的更多相关文章
- dom4j解析示例
收藏信息.xml <?xml version="1.0" encoding="GB2312" standalone="no"?> ...
- DOM解析示例
收藏信息.xml <?xml version="1.0" encoding="GB2312" standalone="no"?> ...
- flask 反向解析示例
1 静态网页 和动态网页 1 静态网页:无法与服务器做动态交互的网页 2 动态网页:允许与服务器做动态加护的 2 WEB 与 服务器 1 WEB :网页(HTML,css,JS) 3 服务器的作用: ...
- json系列(一)cjson,rapidjson,yyjson解析示例
前言 项目上通过消息中间件传输json格式的数据,其他接收模块需要对json格式的数据进行解析,反序列化.对json解析工具有几个关注点,一是具备解析和构造的基础功能,二是具备解析和构造的高性能,三是 ...
- 一个由正则表达式引发的血案 vs2017使用rdlc实现批量打印 vs2017使用rdlc [asp.net core 源码分析] 01 - Session SignalR sql for xml path用法 MemCahe C# 操作Excel图形——绘制、读取、隐藏、删除图形 IOC,DIP,DI,IoC容器
1. 血案由来 近期我在为Lazada卖家中心做一个自助注册的项目,其中的shop name校验规则较为复杂,要求:1. 英文字母大小写2. 数字3. 越南文4. 一些特殊字符,如“&”,“- ...
- 转:一个Sqrt函数引发的血案
转自:http://www.cnblogs.com/pkuoliver/archive/2010/10/06/1844725.html 源码下载地址:http://diducoder.com/sotr ...
- 一个Sqrt函数引发的血案(转)
作者: 码农1946 来源: 博客园 发布时间: 2013-10-09 11:37 阅读: 4556 次 推荐: 41 原文链接 [收藏] 好吧,我承认我标题党了,不过既然你来了, ...
- 【转载】一个Sqrt函数引发的血案
转自:http://www.cnblogs.com/pkuoliver/archive/2010/10/06/sotry-about-sqrt.html 源码下载地址:http://diducoder ...
- 一个Sqrt函数引发的血案
源码下载地址:http://diducoder.com/sotry-about-sqrt.html 好吧,我承认我标题党了,不过既然你来了,就认真看下去吧,保证你有收获. 我们平时经常会有一些数据运算 ...
随机推荐
- FastAdmin 增删改查在哪里?
FastAdmin 增删改查在哪里? 一键生成 CRUD think crud -t test -u 1 执行命令合会在 controller 下生成 Test.php 控制器. 但是这个文件里确看不 ...
- [Client] looks like we got no XML document in....
无wsdl方式应用webservice时,服务端包含了 include_once'inc/utility_all.php '一直报[Client] looks like we got no XML d ...
- 初识C++ 中的STL
首先,STL是指standard template library,即C++标准模板库,也叫泛型库. STL提供了三种类型的组件:容器,迭代器和算法,他们都支持泛型程序设计标准. (一)容器: 分为: ...
- 监控mysql 脚本
监控mysql脚本 http://oldboy.blog.51cto.com/2561410/986905
- 远程debug配置
1. 第一步,debugconfig 2. 在启动文件中添加 Set JAVA_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,address=,server=y ...
- xunsearch使用SCWS
目录 创建分词对象 获取分词结果 提取重要词汇 在 Xunsearch 使用 SCWS 创建分词对象 $xs = new XS('my'); // 必须先创建一个 xs 实例,否则会抛出异常 $tok ...
- Linux内核中常见内存分配函数
1. 原理说明 Linux内核中采用了一种同时适用于32位和64位系统的内存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系统中,用到了四级页表,如图2-1所示.四级页表分 ...
- JavaWeb---总结(十九)Session机制
一.术语session session,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作/消息,比如打电话时从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个session.有时候我们可 ...
- VC6编写的Dll调试方法
Dll工程运行时指定调用exe程序. 关键!!往往被忽略:exe中也一定要指向此调用dll,如果指向不对,什么效果也没有!
- leetcode669
本题目是使用递归处理,根据当前的值来判断剪去的子树,保留剩下的子树. class Solution { public: TreeNode* trimBST(TreeNode* root, int L, ...