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