收集Nokogiri中节点的几个元素中的第一个

时间:2013-03-18 20:08:40

标签: ruby xml nokogiri

我有一个非常简单的问题,我完全无法找到答案。我对Ruby和Nokogiri有非常基本的了解。

我的数据看起来像:

<release> 
 <artists>
  <artist>
   <name>Johnny Mnemonic</name>
  </artist>
  <artist>
    <name>Constantine</name>
  </artist>
 <artists>
</release>
<release>
 <artists>
  <artist>
   <name>Speed</name>
  </artist>
  <artist>
    <name>The Matrix</name>
  </artist>
 <artists>
 </release>
 . . .and so on.

对于每个版本,我只想要第一个标签中的数据。我尝试了以下代码,但它从艺术家那里得到了所有代码:

page = Nokogiri::XML(open("37.xml"))

page.xpath("//artists[1]").each do |el|

File.open("#{LOCAL_DIR}/37.txt", 'a'){|f| f.write(el)}

任何有关正确方向的帮助或指示都会非常感激。

2 个答案:

答案 0 :(得分:7)

Nokogiri支持两种主要类型的搜索,searchatsearch返回一个NodeSet,您应该将其视为一个数组。 at返回一个节点。要么可以采用CSS或XPath表达式。我更喜欢CSS,因为它们更具可读性,但有时候你很容易找到你想要的地方,所以试试另一个。

对于您的问题,使用text指定要从中提取文本的节点非常重要。如果您的结果过于宽泛,除了您想要的标记内的文字之外,您还会从标记之间获取文字。为了避免深入到最直接的节点,直到您尝试阅读的内容:

require 'nokogiri'

doc = Nokogiri::XML(<<EOT)
<release> 
<artists>
  <artist>
  <name>Johnny Mnemonic</name>
  </artist>
  <artist>
    <name>Constantine</name>
  </artist>
<artists>
<release>
EOT

因为它们专门查找name节点,所以在没有垃圾的情况下很容易获得所需的文本:

doc.at('name').text                # => "Johnny Mnemonic"
doc.at('artist name').text         # => "Johnny Mnemonic"
doc.at('artists artist name').text # => "Johnny Mnemonic"

这些是更宽松的搜索,因此返回更多垃圾:

doc.at('artist').text  # => "\n   Johnny Mnemonic\n  "
doc.at('artists').text # => "\n  \n   Johnny Mnemonic\n  \n  \n    Constantine\n  \n \n\n"

使用search返回多个节点:

doc.search('name').map(&:text)

[
    [0] "Johnny Mnemonic",
    [1] "Constantine"
]

doc.search('artist').map(&:text)

[
    [0] "\n   Johnny Mnemonic\n  ",
    [1] "\n    Constantine\n  "
]

searchat之间唯一真正的区别是atsearch(...).first类似。

参见&#34; How to avoid joining all text from Nodes when scraping&#34;还

为方便起见,Nokogiri还有一些额外的别名:at_csscss,以及at_xpathxpath


以下是另一种方法,使用CSS和XPath访问器获取名称,从Pry剪切:

[5] (pry) main: 0> # using CSS with Ruby
[6] (pry) main: 0> artists = doc.search('release').map{ |release| release.at('artist').text.strip }
[
    [0] "Johnny Mnemonic",
    [1] "Speed"
]
[7] (pry) main: 0> # using CSS with less Ruby
[8] (pry) main: 0> artists = doc.search('release artists artist:nth-child(1) name').map{ |n| n.text }
[
    [0] "Johnny Mnemonic",
    [1] "Speed"
]
[9] (pry) main: 0>
[10] (pry) main: 0> # using XPath
[11] (pry) main: 0> artists = doc.search('release/artists/artist[1]/name').map{ |t| t.content }
[
    [0] "Johnny Mnemonic",
    [1] "Speed"
]
[12] (pry) main: 0> # using more XPath
[13] (pry) main: 0> artists = doc.search('release/artists/artist[1]/name/text()').map{ |t| t.content }
[
    [0] "Johnny Mnemonic",
    [1] "Speed"
]

答案 1 :(得分:0)

您的xpath表达式会选择<artists>,而不是您期望的每个<artist>标记。请尝试:

doc.search('artists artist').map(&:text)

您的表达式"//artists"将检索所有“艺术家”标签,[1]会选择第一个标签,而不是标签内部的第一个元素。