我正在制作一个小型Rails应用程序,用于解析本地公共广播电台的HTML播放列表,并显示当前播放的歌曲。
我创建了一个类来对播放列表中的歌曲进行建模,如下所示:
require 'open-uri'
class Song
attr_accessor :artist, :title, :album, :playtime
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def self.latest(how_many)
html = Nokogiri::HTML(open(Rails.configuration.on_air_url))
rows = html.css('#table_tracklist tbody tr')
rows.take(how_many).map do |row|
parse_song(row)
end
end
private
def self.parse_song(row)
artist = row.css('.artist').text
playtime = row.css('.time span').text
title = row.css('.song').text
album = row.css('.album').text
Song.new({ artist: artist, playtime: playtime, title: title, album: album })
end
end
我对此有几个问题:
- 我没有使用任何ActiveRecord或ActiveModel功能。这仍然属于我的
models
目录中的一个类吗?还是我应该将它重构为lib
中的类?我计划有一个控制器,它的唯一目的是将通过JSON播放的最新歌曲返回到客户端。有更好的方法吗 - 我对
Song::latest
方法很满意,但我觉得应该有一种更优雅的方法来做Song::parse_song
。我正在考虑更改我的模型的属性,以匹配播放列表使用的CSS类的名称,并使用我想要获取的属性名称数组,但由于存在"时间"字段的特殊情况(我想要获取跨度的文本),这样似乎会更清楚。你能提出一些建议吗
我认为删除我的initialize方法并这样做会更好。想法?[注:包含锡人的答案如下。]
def self.parse_song(row)
song = Song.new
song.artist = row.at_css('.artist').text
song.playtime = row.at_css('.time span').text
song.title = row.at_css('.song').text
song.album = row.at_css('.album').text
song
end
您不了解css
的作用:
artist = row.css('.artist').text
playtime = row.css('.time span').text
title = row.css('.song').text
album = row.css('.album').text
应该是:
artist = row.at('.artist').text
playtime = row.at('.time span').text
title = row.at('.song').text
album = row.at('.album').text
css
与search
和xpath
一样返回NodeSet。NodeSet就像一个节点数组。即使您知道文档中只有一个匹配元素,css
仍然会返回一个集合。如果某个特定选择器有多个命中,您将收到所有匹配的节点。
当你在NodeSet上使用text
时,你会得到节点中所有文本的串联字符串,这很可能不是你想要的:
require 'nokogiri'
doc = Nokogiri::HTML(<<EOT)
<html>
<body>
<p>foo</p>
<p>bar</p>
</body>
</html>
EOT
doc.css('p').text # => "foobar"
此外,当涉及到我们用来与之交谈的代码时,Nokogiri非常宽容/理解。我们不必使用css
或xpath
,我们可以使用search
,让Nokogili判断选择器是CSS还是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
at
、at_css
和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"
我建议你不要懒惰,在编写代码搜索节点的99.9%的时间里使用search
和at
,然后在那些不得不向Nokogiri提示选择器是什么的时候使用CSS/XPath变体