You don't understand what css
does:
artist = row.css('.artist').text
playtime = row.css('.time span').text
title = row.css('.song').text
album = row.css('.album').text
Which should be:
artist = row.at('.artist').text
playtime = row.at('.time span').text
title = row.at('.song').text
album = row.at('.album').text
css
, like search
and xpath
returns a NodeSet. A NodeSet is like an array of Nodes. Even if you know there is only one matching element in the document, css
will still return a set. If there are multiple hits for a particular selector, you're going to receive all nodes that match.
When you use text
on a NodeSet you're going to get a concatenated string of all the text in the nodes, which is most likely not what you want:
require 'nokogiri'
doc = Nokogiri::HTML(<<EOT)
<html>
<body>
<p>foo</p>
<p>bar</p>
</body>
</html>
EOT
doc.css('p').text # => "foobar"
Also, Nokogiri is very forgiving/understanding when it comes to the code we use to talk to it. We don't have to use css
, or xpath
, we can use search
and let Nokogiri figure out whether the selector is CSS or XPath:
require 'nokogiri'
doc = Nokogiri::HTML(<<EOT)
<html>
<body>
<p>foo</p>
<p>bar</p>
</body>
</html>
EOT
doc.css('p').size # => 2
doc.search('p').size # => 2
The same is true for at
, at_css
and at_xpath
:
require 'nokogiri'
doc = Nokogiri::HTML(<<EOT)
<html>
<body>
<p>foo</p>
<p>bar</p>
</body>
</html>
EOT
doc.at_css('p').text # => "foo"
doc.at('p').text # => "foo"
I recommend being lazy and using search
and at
for 99.9% of the times you write code searching for nodes, then use the CSS/XPath variant for those oh-so-few times when you have to give Nokogiri a hint what the selector is.