我正在使用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
?我不确定。
答案 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']
是一种不好的做法。
相反,请考虑使用at
或at_css
代替search
或css
:
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