在使用 XPATH 时,WebDriver 将始终依赖于目标浏览器。从技术上讲,它只是连接浏览器的一个花哨的桥梁(无论浏览器是 Firefox 还是 Chrome - IE 最高 11 不支持 XPATH)
不幸的是,驻留在浏览器内存中的 DOM(元素和属性结构)与您可能提供给 Html Agility Pack 的 DOM 不同。
如果您使用浏览器内存中的 DOM 内容加载 HAP(例如等效于 document.OuterHtml),则可能是相同的。
通常情况并非如此,因为开发人员在没有浏览器的情况下使用 HAP 来报废网站,因此他们从网络流(来自 HTTP GET 请求)或原始文件中提供数据。
这个问题很容易演示。例如,如果您创建的文件仅包含以下内容:
<table><tr><td>hello world</td></tr></table>
(没有html,没有body标签,这实际上是一个无效的html文件)
使用 HAP,您可以像这样加载它:
HtmlDocument doc = new HtmlDocument();
doc.Load(myFile);
HAP 将提出的结构是这样的:
+table
+tr
+td
'hello world'
HAP 不是一个浏览器,它是一个解析器,它并不真正了解 HTML 规范,它只知道如何解析一堆标签并用它构建一个 DOM。例如,它不知道一个文档应该以 HTML 开头,并且应该包含一个 BODY,或者当一个 TABLE 元素被浏览器推断时总是有一个 TBODY 子元素。
但在 Chrome 浏览器中,如果你打开这个文件,检查它并询问 XPATH 的 TD 元素,它会报告这个:
/html/body/table/tbody/tr/td
因为 Chrome 是自己编造出来的……正如您所见,这两个系统不匹配。
请注意,如果您在源 HTML 中有可用的 id 属性,则故事会更好,例如,使用以下 HTML:
<table><tr><td id='hw'>hello world</td></tr></table>
Chrome 会报告以下 XPATH(它会尽量使用id 属性):
//*[@id="hw"]
Wich 也可用于 HAP。但是,这并不总是有效。例如,使用以下 HTML
<table id='hw'><tr><td>hello world</td></tr></table>
Chrome 现在将生成此 XPATH 到 TD:
//*[@id="mytable"]/tbody/tr/td
正如您所见,由于推断的 TBODY,这在 HAP 中不再可用。
因此,最终,您不能在那些浏览器之外的其他上下文中盲目地使用浏览器生成的 XPATH。在其他情况下,您将不得不找到其他判别式。
实际上,我个人认为这在某种程度上是一件好事,因为它会使您的 XPATH 更能抵抗更改。但你必须考虑:-)
现在让我们回到你的案例 :)
以下 C# 示例控制台案例应该可以正常工作:
static void Main(string[] args)
{
var web = new HtmlWeb();
var doc = web.Load("http://www2.epa.gov/languages/traditional-chinese");
var node = doc.DocumentNode.SelectSingleNode("//section[@id='main-content']//div[@class='pane-content']//a");
Console.WriteLine(node.OuterHtml); // displays <a href="http://www.oehha.ca.gov/fish/pdf/59329_CHINESE.pdf">...etc...</a>"
}
如果您查看流或文件的结构(甚至浏览器显示的内容,但要小心,避免 TBODY...),最简单的方法是
- 找到
id(就像浏览器一样)和/或
- 递归或不递归查找此下方的唯一子元素或孙子元素或属性
- 避免过于精确的 XPATH。
p/p/p/div/a/div/whatever 之类的东西不好
所以,在这里,在 main-content id 属性之后,我们只查找(使用 // 递归)具有特殊类的 DIV,并且我们(再次递归)查找第一个可用的子 A .
这个 XPATH 应该在 webdriver 和 HAP 中工作。
请注意,此 XPATH 也有效://div[@class='pane-content']//a 但对我来说它看起来有点松散。设置id 属性通常是个好主意。