C# XPath在浏览器和HtmlAgilityPack中给出不同的结果

C# XPath在浏览器和HtmlAgilityPack中给出不同的结果,c#,xpath,html-agility-pack,C#,Xpath,Html Agility Pack,我试图在C#程序中使用HtmlAgilityPack解析网页的一部分。以下是本节页面的简化版本(美国东部时间2015年1月30日下午2:40编辑): 但是,当我使用HttpWebRequest加载此页面,将响应流加载到HtmlDocument对象中,并使用此xpath调用其DocumentNode属性上的SelectNodes(xpath)时,它不仅返回三个正确的节点,还返回上面示例中文本为“错误选择”的两个标记。我注意到,这实际上与我使用上面的XPath相同,只是没有最后一个“[1]”,如下所

我试图在C#程序中使用HtmlAgilityPack解析网页的一部分。以下是本节页面的简化版本(美国东部时间2015年1月30日下午2:40编辑):

但是,当我使用HttpWebRequest加载此页面,将响应流加载到HtmlDocument对象中,并使用此xpath调用其DocumentNode属性上的SelectNodes(xpath)时,它不仅返回三个正确的节点,还返回上面示例中文本为“错误选择”的两个标记。我注意到,这实际上与我使用上面的XPath相同,只是没有最后一个“[1]”,如下所示(为便于阅读而包装):

//div[@id=“main box”]/div/div[2]/div[contains(@class,“行框”)]/div[
(位置()=3或位置()=4)和后代::a[
包含(@href,“a=)
]
][1] /genderant::a[包含(@href,“a=)]
我确保使用了最新版本的HtmlAgilityPack,尝试了XPath的几种变体,以确定它是否遇到了任意的最大长度或其他类似的简单问题,并尝试研究类似问题,但没有成功。我尝试使用相同的基本概念组合一个更简单的HTML结构进行测试,但无法重现这个问题,因此我怀疑这可能是HtmlAgilityPack如何解析此结构中的某些内容的一些微妙问题

如果有人知道什么可能导致这个问题,或者有更好的方法来编写XPath表达式,以获得正确的节点,并且希望不会在HtmlAgilityPack中引起问题,我将非常感激

编辑

正如所建议的,这里是我正在使用的C代码的简化版本,我已经确认它确实为我重现了这个问题

using System;
using System.Net;
using HtmlAgilityPack;

...

static void Main(string[] args)
{
    string url = "http://www.deerso.com/test.html";
    string xpath = "//div[@id=\"main-box\"]/div/div[2]/div[contains(@class, \"row-box\")]/div[(position() = 3 or position() = 4) and descendant::a[contains(@href, \"a=\")]][1]/descendant::a[contains(@href, \"a=\")][1]";
    int statusCode;
    string htmlText;

    HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);

    request.Accept = "text/html,*/*";
    request.Proxy = new WebProxy();
    request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0";

    using (var response = (WebResponse)request.GetResponse())
    {
        statusCode = (int)((HttpWebResponse)response).StatusCode;
        using (var stream = response.GetResponseStream())
        {
            if (stream != null)
            {
                using (var reader = new System.IO.StreamReader(stream))
                {
                    htmlText = reader.ReadToEnd();
                }
            }
            else
            {
                Console.WriteLine("Request to '{0}' failed, response stream was null", url);
                htmlText = null;
                return;
            }
        }
    }

    HtmlNode.ElementsFlags.Remove("form"); //fix for forms
    HtmlDocument doc = new HtmlDocument();
    doc.LoadHtml(htmlText);

    HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes(xpath);

    foreach (HtmlNode node in nodes)
    {
        Console.WriteLine("Node Found:");
        Console.WriteLine("Text: {0}", node.InnerText);
        Console.WriteLine("Href: {0}", node.Attributes["href"].Value);
        Console.WriteLine();
    }

    Console.WriteLine("Done!");
}
我试图在类为“row box”的每个div的第3或第4个子div中获取第一个也是唯一第一个带有get参数“a”的“a”标记

我认为这样的查询在单个XPath表达式中是不可能的。在XQuery中这将非常容易:

//div[contains(@class,'row-box')中$rowBox的

让$firstrelated:=($rowBox/div)[
(位置()=3或位置()=4)
和//a[包含(@href,'a=')]
])[1]
return($firstRelevant//a[contains(@href,'a=')])[1]
但是这里进行的谓词分组(即,
(…)[…]
)的数量超过了XPath的表达能力

在C#中通过多个步骤选择结果将是一种方法,与XQuery的做法大致相同:

  • 对于每个
    //div[包含(@class,'row-box')]
    • 选择
      /div[(位置()=3或位置()=4)和//a[包含(@href,'a=')]
    • 对于第一个:
      • 选择
        //a[包含(@href,'a=')]
      • 就拿第一个吧

基于更新的Html的新答案

我们不能使用
//a[contains(@href,'a=')][1]
过滤器,因为这是选择第一个
作为指南,我将xpath表达式放在一起:

//div[contains(@class,'row-box')]        -> Get nodeset of <div class="*row-box*"> elements
/descendant::a                           -> From here get all descendant <a> elements
[contains(@href,'a=') and position()=1]  -> Filter according to href value and element being the first descendant
当我在HtmlAgilityPack中运行它时,只返回以下三个元素:

<a href = "/test/path?a=123">
<a href = "/test/path?a=abc&b=123">
<a href = "/test/path?a=ghi">

下面是表达式的分解:

//div[contains(@class,'row-box')]        -> Get nodeset of <div class="*row-box*"> elements
/descendant::a                           -> From here get all descendant <a> elements
[contains(@href,'a=') and position()=1]  -> Filter according to href value and element being the first descendant
//div[contains(@class,'row-box')]->获取元素的节点集
/后代::a->从这里获取所有后代元素
[包含(@href,'a=')和位置()=1]->根据href值和作为第一个子代的元素进行筛选

我相信您的问题中xpath的关键区别在于
/genderant::a[contains(@href,'a=')和position()=1]
/genderant::a[contains(@href,'a=')][1]
。应用
[1]
分别作为第一个子代而不是第一子代进行筛选。

也许您也可以共享C代码并将问题标记为C代码?然后,人们可以尝试重现问题。@MathiasMüller感谢您的建议,我添加了重现问题的C代码。我看到了索引器和XPath与h以前使用过HTML敏捷包。使用Linq2Html语法通常是可行的。我没有时间调试HAP以找出哪里出了问题。不幸的是,我对XQuery了解不多,但我同意用C#分解它是可行的。不幸的是,由于我的程序的复杂性超出了问题的范围,它需要e跨多个组件和数据库进行了广泛的更改…我提供的XPath在浏览器中测试时满足了我的要求,只是出于某种原因没有在HtmlAgilityPack中测试。不过,我肯定会研究XQuery,在我的情况下,它听起来可能是一个可行的解决方案。我倾向于说,使用HTML Agility Pack得到的结果是正确的我有点惊讶,你的表达式可以在浏览器中工作。它不应该。你能详细解释一下为什么你说它不应该在浏览器中工作吗?也许它可以为我的问题提供一些有价值的见解。这确实适用于最初给出的HTML。但令人尴尬的是,整个页面包含了我忽略的其他a标记,因为它们不是影响我所拥有的。我已经编辑了问题中的HTML,以包括两个丢弃的HTML,一个在第一行框中,另一个在第二行框中。问题是将其设置为带“and”的复合谓词意味着它必须是标记子代的第一个,并且具有GET参数。将其设置为
//div[contains(@class,'row-box')]/degenant::a[contains(@href,“?a=”)][1]
在浏览器中工作,但在HtmlAgilityPack中选择更多额外节点。更新答案以考虑位置()=1筛选器,排除添加到html中的标记的有效结果。
// Get the <div> elements we know are ancestors to the <a> elements we want
HtmlNodeCollection topDivs = doc.DocumentNode.SelectNodes("//a[contains(@href,'?a=')]/ancestor::div[contains(@class,'row-box')]");

// Create a new list to hold the <a> elements
List<HtmlNode> linksWeWant = new List<HtmlNode>(topDivs.Count)

// Iterate through the <div> elements and get the first descendant
foreach(var div in topDivs)
{
    linksWeWant.Add(div.SelectSingleNode("(//a[contains(@href,'?a=')])[1]"));
}
<a href = "/test/path?a=123">
<a href = "/test/path?a=abc&b=123">
<a href = "/test/path?a=ghi">
//div[contains(@class,'row-box')]        -> Get nodeset of <div class="*row-box*"> elements
/descendant::a                           -> From here get all descendant <a> elements
[contains(@href,'a=') and position()=1]  -> Filter according to href value and element being the first descendant