【背景】

之前使用HtmlAgilityPack期间,遇到了2个bug:

1. InnerText没有包含对应字符串(但是用NextSibling.InnerText却可以得到)

对于html:

1
<option value="search-alias=instant-video">Amazon Instant Video</option>

用如下的代码:

1
2
3
4
//<option value="search-alias=instant-video">Amazon Instant Video</option>
string searchValue = singleOptionNode.Attributes["value"].Value; //search-alias=instant-video
//instant-video
string generalCategory = singleOptionNode.InnerText; //CAN NOT get: Amazon Instant Video

是不工作的。

后来经过调试,改为:

1
2
3
4
//<option value="search-alias=instant-video">Amazon Instant Video</option>
string searchValue = singleOptionNode.Attributes["value"].Value; //search-alias=instant-video
//instant-video
string generalCategory = singleOptionNode.NextSibling.InnerText; //can get: Amazon Instant Video

却是可以的。

很是尼玛的诡异。

很明显是一个bug。

和:

2.丢失了form节点的input子节点

访问:

http://www.amazon.com/gp/offer-listing/B0083PWAPW/ref=dp_olp_all_mbc?ie=UTF8&condition=all

得到的html中,对应的部分是:

1
2
3
4
5
6
7
8
9
10
11
12
13
<form method="POST" action="/gp/item-dispatch/ref=olp_atc_fm_1" >
    <input type="hidden" name="session-id" value="182-0726239-4848949">
    <input type="hidden" name="qid" value="">
    <input type="hidden" name="sr" value="">
    <input id="signInToHUC" type="hidden" value="0" name="signInToHUC">
    <input type="hidden" name="metric-asin.B0083PWAPW" value="1">
    <input type="hidden" name="registryItemID.1" value="">
    <input type="hidden" name="registryID.1" value="">
    <input type="hidden" name="itemCount" value="1">
    <input type="hidden" name="offeringID.1"value="%2F%2FeHHmpktM3oPoQj%2FOWhDI%2FpHyvwwFCwEfNIBEgFcfAHzKHAzVK%2BZfhkmBFO%2BPbow9JfdOmrE6eKME4ydhLTTK1Dgaf8O3N7SyOR%2F136TvVh0lfJypEt4Q%3D%3D">
    <input type="hidden" name="isAddon" value="0">
    <input type="image" src="http://g-ecx.images-amazon.com/images/G/01/x-locale/nav2/images/add-to-cart-md-p._V192250398_.gif" align="absmiddle"alt="Add to cart" border="0" height="21" name="submit.addToCart" width="112"/>
</form>

可以通过:

1
2
htmlDoc = crl.htmlToHtmlDoc(respHtml);
HtmlNodeCollection postItemNodeList = htmlDoc.DocumentNode.SelectNodes("//form[starts-with(@action, '/gp/item-dispatch/ref=') and @method='POST']");

搜索到form节点,但是结果其下,再去搜input节点:

1
HtmlNodeCollection inputTypeNodeList = postItemNode.SelectNodes(".//input[@type='hidden' and @name and @value]");

竟然得到的inputTypeNodeList是null:

即form下面,没有找到任何的child,即,所有的input节点,都丢失了!

再回去查看postItemNode,结果其下就是没有child的:

所以,应该是对应的HtmlAgilityPack的bug。

【折腾过程】

1. 后来看到:

No child nodes for FORM object

中提到了,说是:

In Html specification form tag can overlap, so Htmlagilitypack handle this node a little different.  
。。。

After adding this call all form elements are added as children.

然后就去看看,结果果然是从child变成了sibling了,而且此处还是很变态的,NextSibling的NextSibling才是我们要的input节点:

所以,此处,看来只能是说动的,类似于上面那个问题一样的,写成NextSibling的NextSibling

不过,真是这样写的话,那也够变态的。。。。

2.然后也看到别人也遇到同样问题:

Problem parsing children of a node with HtmlAgilityPack

而且某人也是放弃了HtmlAgilityPack而转到了SGMLReader了。

不过,另外有人说,不是bug,而是可以配置的。

其相关的讨论见:

http://htmlagilitypack.codeplex.com/workitem/21782

再参考:

HtmlAgilityPack — Does <form> close itself for some reason?

去,在将html转为htmlDoc之前,添加:

1
HtmlNode.ElementsFlags.Remove("form");

变为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
HtmlNode.ElementsFlags.Remove("form");
 
htmlDoc = crl.htmlToHtmlDoc(respHtml);
HtmlNodeCollection postItemNodeList = htmlDoc.DocumentNode.SelectNodes("//form[starts-with(@action, '/gp/item-dispatch/ref=') and @method='POST']");
if (postItemNodeList == null)
{
    //something error
}
else
{
    foreach (HtmlNode postItemNode in postItemNodeList)
    {
        string itemDispatchUrl = postItemNode.Attributes["action"].Value; ///gp/item-dispatch/ref=olp_atc_used_1
        itemDispatchUrl = constAmazonDomainUrl + itemDispatchUrl;//http://www.amazon.com/gp/item-dispatch/ref=olp_atc_used_1
 
        Dictionary<stringstring> postDict = new Dictionary<stringstring>();
 
        HtmlNodeCollection inputTypeNodeList = postItemNode.SelectNodes(".//input[@type='hidden' and @name and @value]");

然后得到的inputTypeNodeList,的确不是null了,也有了child了:

【总结】

之前还夸奖HtmlAgilityPack好用呢,结果还没用多久,就出现这么多的bug。看来真的没法继续使用了。

每次都要很小心,不知道啥时候就会出错,真郁闷。。。

即使不是bug,其本身把form下面的节点,都弄成其sibling这个策略,还是很变态的。至少让更多人的,都容易误解。


【后记】

后来的后来,经过参考别人的解释:

<option> have no child, why?

发现,

其实上述两个,所谓的bug,就是同一个问题:

对于HtmlAgilityPack,实际上,对于option,form等tag,其默认的处理的结果是:其下的子节点,会变成sibling

所以,上面的:

对于option需要通过NextSibling才能获得对应的InnerText;

对于form子节点为空,也是需要通过NextSibling(的NextSibling)才能获得对应的input子节点;

其本质都是:

HtmlAgilityPack是针对HTML 3.2的规范去实现的,而HTML 3.2就是这样规定的。

其不是bug,而是feature

但是很明显,是属于让人蛋疼的feature。

解决办法有两种:

1.改源码

把HtmlNode.cs中的下面这行注释掉:

1
ElementsFlags.Add("form", HtmlElementFlag.CanOverlap | HtmlElementFlag.Empty);

2.不改源码

在HtmlDocument类型的变量执行LoadHtml之前,加上:

1
HtmlNode.ElementsFlags.Remove("tagName");

即,对于我之前的crifanlib.cs中的:

1
2
3
4
5
6
7
8
public HtmlAgilityPack.HtmlDocument htmlToHtmlDoc(string html)
{
    HtmlAgilityPack.HtmlDocument htmlDoc = newHtmlAgilityPack.HtmlDocument();
 
    htmlDoc.LoadHtml(html);
 
    return htmlDoc;
}

换成:

1
2
3
4
5
6
7
8
9
10
11
12
13
public HtmlAgilityPack.HtmlDocument htmlToHtmlDoc(string html)
{
    HtmlAgilityPack.HtmlDocument htmlDoc = new HtmlAgilityPack.HtmlDocument();
 
    //make some html tag: form/option, has child
    HtmlNode.ElementsFlags.Remove("form");
    HtmlNode.ElementsFlags.Remove("option");
 
    htmlDoc.LoadHtml(html);
 
    return htmlDoc;
}

即可。

如此,后续解析html得到的form,option等tag,其child就是我们所希望的内容了。

另外:

1.对于是否还有其他特殊的html的tag,也是默认被处理为子节点变为sibling的,就不知道了。

2.等有空再去深究背后的那个HTML 3.2规范是怎么定义的。

HtmlAgilityPack中通过sibling才能得到对应的InnerText和form,option等tag的子节点的更多相关文章

  1. 解决HtmlAgilityPack无法获取form标签子节点的问题

    问题描述 今天使用HtmlAgilityPack提取Form表单下的input节点,发现提取的form节点没有子节点,InnerHtml也是为空,起初以为是标签不全导致,后来分析html代码发现不可能 ...

  2. 仅Firefox中A元素包含Select时点击Select不能选择option

    这是在使用京东的一个日期组件时碰到的bug,重现bug的代码精简如下 <!DOCTYPE HTML> <html> <head> <title> 仅Fi ...

  3. C#中treeview的问题,如何区分根节点和子节点以及根节点和根节点的兄弟节点?

    根节点的Level属性为0,一级子节点Level属性为1,二级子节点Level属性为2,以此类推:同级节点可以用索引.名称.文本来区分.用索引区分根节点时,TreeView.Nodes[0]就是第一个 ...

  4. 关于 Unity UGUI 中修改 Mask 组件下 Image 等子节点组件的材质无效的问题

    前几天同事做了一个效果,希望在原本使用了遮罩组件 Mask 的技能图标(让技能图标变成圆形)上在添加一个置灰的功能,但问题来了:因为是动态根据游戏中玩家的条件才动态置灰,以修改 Mask 下子节点 I ...

  5. zTree中父节点禁用,子节点可以用

    参考学习网址:http://www.treejs.cn/v3/main.php#_zTreeInfo zTree中父节点禁用,子节点可以用 axios.get('/base/unit/unittree ...

  6. Web网页树形列表中实现选中父节点则子节点全选和不选中父则子全不选

                需要实现的功能:选中父节点对应子节点全选:不选中父节点,对应子节点也不选中 如下图所示,选中车队,对应车队中车辆也全部选中,以实现车队中所有车辆在地图上的显示. 选中cqupt ...

  7. SQL 用;with 由所有的子节点查询到树结构中所有父节点

    1.所有的子节点查询到树结构中所有父节点 RETURNS @Tree Table(PID )) as begin --DECLARE @ID VARCHAR() --SET @ID = ' ;with ...

  8. 问题:jQuery中遍历XML文件时候,获取子节点children不支持的情况(已解决)

    问题描述: 今天在写一个基于 jquery 的读取xml文件的程序时候,需要遍历xml的节点. 代码片段如下: function parse_xml_node(parent,result){ // r ...

  9. js中创建html标签、加入select下默认的option的value和text、删除select元素节点下全部的OPTION节点

    <pre name="code" class="java"> jsp 中的下拉框标签: <s:select name="sjx&qu ...

随机推荐

  1. Coursera Machine Learning : Regression 多元回归

    多元回归 回顾一下简单线性回归:一个特征,两个相关系数 实际的应用要比这种情况复杂的多,比如 1.房价和房屋面积并不只是简单的线性关系. 2.影响房价的因素有很多,不仅仅是房屋面积,还包括很多其他因素 ...

  2. Windows XP SP3 VC6环境下成功编译openssl-0.9.8zh

    1.下载openssl-0.9.8zh解压到f:\openssl-0.9.8zh 下载nasm-2.12.03rc1解压到D:\develop\nasm-2.12.03rc1并把添加到系统环境变量PA ...

  3. SQL三大范式三个例子搞定

    第一范式(1NF) (必须有主键,列不可分) 数据库表中的任何字段都是单一属性的,不可再分 create table aa(id int,NameAge varchar(100)) insert aa ...

  4. Mac删除.DS_Store文件

    1.删除.DS_Store文件 sudo find ./ -name ".DS_Store" -depth -exec rm {} \; 2.禁止生成此文件 defaults wr ...

  5. Docker学习<一>--初体验Windows环境下安装

    背景 今天想试用spring boot与jwt协议的实现,配套就需要使用redis,但redis似乎windows环境版本部署起来不是那么舒心,果断尝试使用docker. 下载 下载地址: 稳定版:h ...

  6. 【windows 下安装 mysql-server 无法登录问题解决】

    ----------------------------- 无感的首行 ----------------------------- 新版 mysql-server 5.7 安装后发现无法使用 mysq ...

  7. FreeMarker标签与使用

    模板技术在现代的软件开发中有着重要的地位,而目前最流行的两种模板技术恐怕要算freemarker和velocity了,webwork2.2对两者都有不错的支持,也就是说在webwork2中你可以随意选 ...

  8. city-picker 选择省市县的一个控件,好用。

    我觉得好奇怪,这么好一个插件,为什么没有设置值的方法,还是我才疏学浅?? 我看有的人做法是把,把源代码里面的自动扫描机制注释掉 // $(function () { // $('[data-toggl ...

  9. java导入excel时遇到的版本问题

    java中读取excel文件时对不同的版本提供了不同的读取方法,这就要求我们在读取excel文件时获取excel文件的版本信息从而通过不同的版本去使用不同的读取方式, 在WorkbookFactory ...

  10. 选择HttpHandler还是HttpModule?

    阅读目录 开始 理解ASP.NET管线 理解HttpApplication 理解HttpHandler 理解HttpModule 三大对象的总结 案例演示 如何选择? 最近收到几个疑问:HttpHan ...