• HtmlAgilityPack中通过sibling才能得到对应的InnerText和form,option等tag的子节点


    【背景】

    之前使用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规范是怎么定义的。

  • 相关阅读:
    转载php在IIS中运行
    程序员必去的网站
    分享一下jQuery UI的地址
    dbcp相关配置
    shell学习第二弹-进阶
    shell学习第一弹-初识
    java servlet 3.0文件上传
    Junit使用第二弹
    各个数据库中,查询前n条记录的方法
    junit使用第一弹
  • 原文地址:https://www.cnblogs.com/mutuan/p/3551794.html
Copyright © 2020-2023  润新知