我有如下数据:
<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.
对于每个版本,我只想要来自第一个<artist>
标记的数据。我尝试了以下代码,但它从艺术家中提取所有文本:
page = Nokogiri::XML(open("37.xml"))
page.xpath("//artists[1]").each do |el|
File.open("#{LOCAL_DIR}/37.txt", 'a'){|f| f.write(el)}
Nokogiri支持两种主要类型的搜索,search
和at
。search
返回一个NodeSet,您应该将其视为一个数组。at
返回一个Node。可以采用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 Mnemonicn "
doc.at('artists').text # => "n n Johnny Mnemonicn n n Constantinen n nn"
使用search
返回多个节点:
doc.search('name').map(&:text)
[
[0] "Johnny Mnemonic",
[1] "Constantine"
]
doc.search('artist').map(&:text)
[
[0] "n Johnny Mnemonicn ",
[1] "n Constantinen "
]
search
和at
唯一真正的区别是at
和search(...).first
很像。
参见"如何避免在抓取时连接节点中的所有文本"。
Nokogiri有一些方便的别名:at_css
和css
, at_xpath
和xpath
。
以下是使用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"
]
xpath表达式选择<artists>
,而不是您所期望的每个<artist>
标记。试试这个:
doc.search('artists artist').map(&:text)
表达式"//artists"
将检索所有的"艺术家"标签,[1]
选择这些标签中的第一个,而不是标签本身的第一个元素。