2013-03-18 22 views
1

我有一個非常簡單的問題,我一直無法找到答案。我對Ruby和Nokogiri有非常基本的瞭解。在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)} 

任何幫助或指針,在格蘭正確的方向將非常感激。

+0

'page.xpath( 「釋放/藝術家/藝術家」)first'? – ted 2013-03-18 20:22:26

回答

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之間的唯一真正的區別在於at就像search(...).first

也參見「How to avoid joining all text from Nodes when scraping」。

引入nokogiri有方便一些額外的別名:at_csscss,並at_xpathxpath


這裏有替代方式,使用CSS和XPath訪問器來獲得的名稱,從撬剪短

[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" 
] 
+0

非常感謝。 doc.at('name')似乎是我想要的。還有一個問題,你能告訴我如何在每個節點上重複這個問題嗎? – user1596069 2013-03-19 18:55:27

+0

啊。你沒有給我們一個你的數據的準確例子。 ''name''不尊重任何包含節點。 ''是否會導致你突破併爲每個人做一些特殊的事情?你應該能夠根據我給你的信息來判斷。 – 2013-03-19 19:38:50

+0

對不起。發佈是第一個節點,其餘的都是其下的孩子。我想從每個版本中提取第一個「名稱」數據,其中可能有10,000個。因此,您的代碼從第一個版本節點中拉出了第一個名稱,我希望該代碼重複每個,結構與第一個相同。 – user1596069 2013-03-19 21:41:16

0

你的XPath表達式選擇<artists>,而不是每個<artist>標籤你似乎expect.Try此:

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

你表達"//artists"會檢索所有的「藝術家」的標籤,該[1]選擇第一項標籤,而不是標籤本身的第一個元素。

相關問題