Nokogiri 支持 XPath 和 CSS 选择器。我通常使用 CSS 是为了可读性和简单性,但 XPath 也很重要,因为它具有强大的功能。
考虑这段代码:
require 'nokogiri'
doc = Nokogiri::HTML(<<EOT)
<html>
<body>
<p id="p1" class="paragraphs" foo="bar">some text</p>
<p id="p2" class="paragraphs" foo="baz">some text</p>
</body>
</html>
EOT
我们可以通过标签找到节点:
p_nodes = doc.search('p')
p_nodes.class # => Nokogiri::XML::NodeSet
p_nodes.size # => 2
p_nodes.map(&:to_html) # => ["<p id=\"p1\" class=\"paragraphs\" foo=\"bar\">some text</p>", "<p id=\"p2\" class=\"paragraphs\" foo=\"baz\">some text</p>"]
使用search 返回一个NodeSet,它类似于一个数组。在这个例子中,它找到了两个<p> 标签。
与使用at相比:
p_node = doc.at('p')
p_node.class # => Nokogiri::XML::Element
p_node = doc.at('p#p2').to_html # => "<p id=\"p2\" class=\"paragraphs\" foo=\"baz\">some text</p>"
p_node = doc.at('p.paragraphs').to_html # => "<p id=\"p1\" class=\"paragraphs\" foo=\"bar\">some text</p>"
at 相当于获取search 找到的第一个元素,但它返回一个元素/节点。一个节点可以包含更多的节点/标签,一个 NodeSet 将是一个节点数组,在所有情况下,一个节点就像指向文档的指针,对于导航很有用。
doc.at('body').at('p') # => #<Nokogiri::XML::Element:0x3fd431448c6c name="p" attributes=[#<Nokogiri::XML::Attr:0x3fd431448c08 name="id" value="p1">, #<Nokogiri::XML::Attr:0x3fd431448bf4 name="class" value="paragraphs">, #<Nokogiri::XML::Attr:0x3fd431448be0 name="foo" value="bar">] children=[#<Nokogiri::XML::Text:0x3fd431448384 "some text">]>
doc.at('body > p') # => #<Nokogiri::XML::Element:0x3fd431448c6c name="p" attributes=[#<Nokogiri::XML::Attr:0x3fd431448c08 name="id" value="p1">, #<Nokogiri::XML::Attr:0x3fd431448bf4 name="class" value="paragraphs">, #<Nokogiri::XML::Attr:0x3fd431448be0 name="foo" value="bar">] children=[#<Nokogiri::XML::Text:0x3fd431448384 "some text">]>
doc.at('p') # => #<Nokogiri::XML::Element:0x3fd431448c6c name="p" attributes=[#<Nokogiri::XML::Attr:0x3fd431448c08 name="id" value="p1">, #<Nokogiri::XML::Attr:0x3fd431448bf4 name="class" value="paragraphs">, #<Nokogiri::XML::Attr:0x3fd431448be0 name="foo" value="bar">] children=[#<Nokogiri::XML::Text:0x3fd431448384 "some text">]>
注意节点的地址如何
Nokogiri::XML::Element:0x3fd431448c6c
在上述结果中保持不变。
在此基础上,要在 HTML 中查找节点,我们可以使用各种参数进行导航:
doc.at('p[foo="baz"]').to_html # => "<p id=\"p2\" class=\"paragraphs\" foo=\"baz\">some text</p>"
doc.search('p[foo="baz"]').size # => 1
doc.search('p[foo="baz"]').first.to_html # => "<p id=\"p2\" class=\"paragraphs\" foo=\"baz\">some text</p>"
这样做的要点是,我们应该检查 HTML,找到让我们获得所需信息的特定节点,然后编写最小选择器以到达那里。如果 HTML 发生变化,长选择器更有可能中断。
最后,小心使用浏览器检查代码,因为它们会弄乱表格。我创建了一个文件,其中包含:
<html>
<body>
<table>
<tr>
<td>foo</td>
</tr>
</table>
</body>
</html>
在 Firefox、Opera 或 Safari 中打开它并检查页面会导致 HTML 已被修改:
<html>
<head></head>
<body>
<table>
<tbody>
<tr>
<td>foo</td>
</tr>
</tbody>
</table>
</body>
</html>
不要信任浏览器,而是使用wget、curl 或 Nokogiri 自己的命令行:
$ nokogiri http://example.com
Your document is stored in @doc...
irb(main):001:0> @doc
=> #<Nokogiri::HTML::Document:0x3fd748d60740 name="document" children=[#<Nokogiri::XML::DTD:0x3fd748d41a5c name="html">, #<Nokogiri::XML::Element:0x3fd748d41750 name="html" children=[#<Nokogiri::XML::Text:0x3fd748d41534 "\n">, #<Nokogiri::XML::Element:0x3fd748d41430 name="head" children=[#<Nokogiri::XML::Text:0x3fd748d41200 "\n ">, #<Nokogiri::XML::Element:0x3fd748d41138 name="title" children=[#<Nokogiri::XML::Text:0x3fd748d40f44 "Example Domain">]>, #<Nokogiri::XML::Text:0x3fd748d40d78 "\n\n ">, #<Nokogiri::XML::Element:0x3fd748d40cb0 name="meta" attributes=[#<Nokogiri::XML::Attr:0x3fd748d40c4c name="charset" value="utf-8">]>, #<Nokogiri::XML::Text:0x3fd748d40544 "\n ">, #<Nokogiri::XML::Element:0x3fd748d40454 name="meta" attributes=[#<Nokogiri::XML::Attr:0x3fd748d403dc name="http-equiv" value="Content-type">, #<Nokogiri::XML::Attr:0x3fd748d403c8 name="content" value="text/html; charset=utf-8">]>, #<Nokogiri::XML::Text:0x3fd748d3d934 "\n ">, #<Nokogiri::XML::Element:0x3fd748d3d858 name="meta" attributes=[#<Nokogiri::XML::Attr:0x3fd748d3d7a4 name="name" value="viewport">, #<Nokogiri::XML::Attr:0x3fd748d3d77c name="content" value="width=device-width, initial-scale=1">]>, #<Nokogiri::XML::Text:0x3fd748d3ce1c "\n ">, #<Nokogiri::XML::Element:0x3fd748d3cd68 name="style" attributes=[#<Nokogiri::XML::Attr:0x3fd748d3cd04 name="type" value="text/css">] children=[#<Nokogiri::XML::CDATA:0x3fd748d3c4bc "\n body {\n background-color: #f0f0f2;\n margin: 0;\n padding: 0;\n font-family: \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n \n }\n div {\n width: 600px;\n margin: 5em auto;\n padding: 50px;\n background-color: #fff;\n border-radius: 1em;\n }\n a:link, a:visited {\n color: #38488f;\n text-decoration: none;\n }\n @media (max-width: 700px) {\n body {\n background-color: #fff;\n }\n div {\n width: auto;\n margin: 0 auto;\n border-radius: 0;\n padding: 1em;\n }\n }\n ">]>, #<Nokogiri::XML::Text:0x3fd748d3c0e8 " \n">]>, #<Nokogiri::XML::Text:0x3fd748d39e38 "\n\n">, #<Nokogiri::XML::Element:0x3fd748d39d48 name="body" children=[#<Nokogiri::XML::Text:0x3fd748d39adc "\n">, #<Nokogiri::XML::Element:0x3fd748d39a00 name="div" children=[#<Nokogiri::XML::Text:0x3fd748d397bc "\n ">, #<Nokogiri::XML::Element:0x3fd748d39460 name="h1" children=[#<Nokogiri::XML::Text:0x3fd748d39118 "Example Domain">]>, #<Nokogiri::XML::Text:0x3fd748d38f60 "\n ">, #<Nokogiri::XML::Element:0x3fd748d38e84 name="p" children=[#<Nokogiri::XML::Text:0x3fd748d38c7c "This domain is established to be used for illustrative examples in documents. You may use this\n domain in examples without prior coordination or asking for permission.">]>, #<Nokogiri::XML::Text:0x3fd748d38ab0 "\n ">, #<Nokogiri::XML::Element:0x3fd748d389fc name="p" children=[#<Nokogiri::XML::Element:0x3fd748d38808 name="a" attributes=[#<Nokogiri::XML::Attr:0x3fd748d387a4 name="href" value="http://www.iana.org/domains/example">] children=[#<Nokogiri::XML::Text:0x3fd748d38344 "More information...">]>]>, #<Nokogiri::XML::Text:0x3fd748d38088 "\n">]>, #<Nokogiri::XML::Text:0x3fd748d35eb4 "\n">]>, #<Nokogiri::XML::Text:0x3fd748d35cfc "\n">]>]>
irb(main):002:0> @doc.at('a').to_html
=> "<a href=\"http://www.iana.org/domains/example\">More information...</a>"
irb(main):003:0> @doc.at('a')['href']
=> "http://www.iana.org/domains/example"