在前10个内找到?

时间:2014-10-02 10:05:09

标签: ruby-on-rails ruby nokogiri

我正在使用Nokogiri屏幕抓取网站的内容。

我设置fetch_number以指定我想要检索的<divs>的数量。例如,我可能想要目标网页上的first(10)推文。

代码如下所示:

doc.css(".tweet").first(fetch_number).each do |item|
  title = item.css("a")[0]['title']
end

但是,如果返回的匹配div标记少于10个,则会报告

NoMethodError: undefined method 'css' for nil:NilClass

这是因为,当找不到匹配的HTML时,它将返回nil。

如何让它返回10内的所有可用数据?我不需要nils。

更新:

task :test_fetch => :environment do
  require 'nokogiri'
  require 'open-uri'
  url = 'http://themagicway.taobao.com/search.htm?&search=y&orderType=newOn_desc'
  doc = Nokogiri::HTML(open(url) )
  puts doc.css(".main-wrap .item").count
  doc.css(".main-wrap .item").first(30).each do |item_info|
    if item_info
      href = item_info.at(".detail a")['href']
      puts href
    else
      puts 'this is empty'
    end
  end
end

返回结果(接近结束):

24
http://item.taobao.com/item.htm?id=41249522884
http://item.taobao.com/item.htm?id=40369253621
http://item.taobao.com/item.htm?id=40384876796
http://item.taobao.com/item.htm?id=40352486259
http://item.taobao.com/item.htm?id=40384968205
.....
http://item.taobao.com/item.htm?id=38843789106
http://item.taobao.com/item.htm?id=38843517455
http://item.taobao.com/item.htm?id=38854788276
http://item.taobao.com/item.htm?id=38825442050
http://item.taobao.com/item.htm?id=38630599372
http://item.taobao.com/item.htm?id=38346270714
http://item.taobao.com/item.htm?id=38357729988
http://item.taobao.com/item.htm?id=38345374874
this is empty
this is empty
this is empty
this is empty
this is empty
this is empty

count仅报告24个元素,但它返回30个数组。 它实际上不是一个数组,但是Nokogiri::XML::NodeSet?我不确定。

3 个答案:

答案 0 :(得分:1)

试试这个

doc.css(".tweet").first(fetch_number).each do |item|
  title = item.css("a")[0]['title'] rescue nil
end

让我知道它有效吗?它不会显示错误

答案 1 :(得分:1)

尝试compact

[1, nil, 2, nil, 3] # => [1, 2, 3]

http://www.ruby-doc.org/core-2.1.3/Array.html#method-i-compact

(即:first(fetch_number).compact.each do |item|

答案 2 :(得分:1)

title = item.css("a")[0]['title']

是一种不好的做法。

相反,请考虑使用atat_css代替searchcss

title = item.at('a')['title']

接下来,如果返回的<a>标记没有title参数,则Nokogiri和/或Ruby会因为title变量为零而感到不安。相反,改进您的CSS选择器只允许匹配<a title="foo">

require 'nokogiri'

doc = Nokogiri::HTML('<body><a href="foo">foo</a><a href="bar" title="bar">bar</a></body>')
doc.at('a').to_html # => "<a href=\"foo\">foo</a>"
doc.at('a[title]').to_html # => "<a href=\"bar\" title=\"bar\">bar</a>"

注意第一个(不限于查找带有title参数的标记的第一个返回第一个<a>标记。使用a[title]只会返回title参数。

这意味着你对值的循环永远不会返回nil,并且你不会在返回的数组中遇到compact它们的问题。

作为一般的编程提示,如果您正在寻找类似的nils,请查看生成数组的代码,因为赔率很高,但它做得并不好。您应该始终知道您的代码将生成什么样的结果。使用compact来清理数组是一种下意识的反应,因为大部分时间都没有正确编写代码。


这是您的更新代码:

require 'nokogiri'
require 'open-uri'
url = 'http://themagicway.taobao.com/search.htm?&search=y&orderType=newOn_desc'
doc = Nokogiri::HTML(open(url) )
puts doc.css(".main-wrap .item").count
doc.css(".main-wrap .item").first(30).each do |item_info|
  if item_info
    href = item_info.at(".detail a")['href']
    puts href
  else
    puts 'this is empty'
  end
end

这就是错误:

doc.css(".main-wrap .item").first(30)

这是一个简单的例子,展示了为什么不起作用:

require 'nokogiri'

doc = Nokogiri::HTML(<<EOT)
<html>
<body>
<p>foo</p>
</body>
</html>
EOT

在Nokogiri中,search', css and xpath`是等效的,除了第一个是通用的,可以采用CSS或XPath,而后两个是特定于该语言。

doc.search('p') # => [#<Nokogiri::XML::Element:0x3fcf360ef750 name="p" children=[#<Nokogiri::XML::Text:0x3fcf360ef4f8 "foo">]>]
doc.search('p').size # => 1
doc.search('p').map(&:to_html) # => ["<p>foo</p>"]

这表明通过执行简单的search返回的NodeSet只返回一个节点,以及该节点的样子。

doc.search('p').first(2) # => [#<Nokogiri::XML::Element:0x3fe3a28d2848 name="p" children=[#<Nokogiri::XML::Text:0x3fe3a28c7b50 "foo">]>, nil]
doc.search('p').first(2).size # => 2

使用first(n)搜索返回&#34; n&#34;元素。如果没有发现Nokogiri使用零值填充它们。

这与我们假设first(n)要做的事情背道而驰,因为Enumerable#first会返回n到n并且不会使用nils。这不是错误,但这是意外的行为,因为Enumerable的first设置了具有该名称的方法的预期行为,但这是NodeSet#first,而不是Enumerable#first ,所以它直到Nokogiri作者改变它才能做到这一点。 (如果你查看特定方法的来源,你可以看到它发生的原因。)

相反,切片NodeSet 会显示预期的行为:

doc.search('p')[0..1] # => [#<Nokogiri::XML::Element:0x3fe3a28d2848 name="p" children=[#<Nokogiri::XML::Text:0x3fe3a28c7b50 "foo">]>]
doc.search('p')[0..1].size # => 1

doc.search('p')[0, 2] # => [#<Nokogiri::XML::Element:0x3fe3a28d2848 name="p" children=[#<Nokogiri::XML::Text:0x3fe3a28c7b50 "foo">]>]
doc.search('p')[0, 2].size # => 1

因此,请勿使用NodeSet#first(n),请使用切片形式NodeSet#[]

应用它,我写代码如下:

require 'nokogiri'
require 'open-uri'

URL = 'http://themagicway.taobao.com/search.htm?&search=y&orderType=newOn_desc'

doc = Nokogiri::HTML(open(URL))

hrefs = doc.css(".main-wrap .item .detail a[href]")[0..29].map { |anchors|
  anchors['href']
}

puts hrefs.size
puts hrefs
# >> 24
# >> http://item.taobao.com/item.htm?id=41249522884
# >> http://item.taobao.com/item.htm?id=40369253621
# >> http://item.taobao.com/item.htm?id=40384876796
# >> http://item.taobao.com/item.htm?id=40352486259
# >> http://item.taobao.com/item.htm?id=40384968205
# >> http://item.taobao.com/item.htm?id=40384816312
# >> http://item.taobao.com/item.htm?id=40384600507
# >> http://item.taobao.com/item.htm?id=39973451949
# >> http://item.taobao.com/item.htm?id=39861209551
# >> http://item.taobao.com/item.htm?id=39545678869
# >> http://item.taobao.com/item.htm?id=39535371171
# >> http://item.taobao.com/item.htm?id=39509186150
# >> http://item.taobao.com/item.htm?id=38973412667
# >> http://item.taobao.com/item.htm?id=38910499863
# >> http://item.taobao.com/item.htm?id=38942960787
# >> http://item.taobao.com/item.htm?id=38910403350
# >> http://item.taobao.com/item.htm?id=38843789106
# >> http://item.taobao.com/item.htm?id=38843517455
# >> http://item.taobao.com/item.htm?id=38854788276
# >> http://item.taobao.com/item.htm?id=38825442050
# >> http://item.taobao.com/item.htm?id=38630599372
# >> http://item.taobao.com/item.htm?id=38346270714
# >> http://item.taobao.com/item.htm?id=38357729988
# >> http://item.taobao.com/item.htm?id=38345374874