我正在使用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内返回所有可用数据?我不需要尼罗河。
更新:
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
?我不确定。
title = item.css("a")[0]['title']
这是一种不好的做法。
相反,考虑使用at
或at_css
而不是search
或css
:进行编写
title = item.at('a')['title']
接下来,如果返回的<a>
标记没有title
参数,那么Nokogiri和/或Ruby将被打乱,因为title
变量将为nil。相反,改进你的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
它们。
作为一个通用的编程技巧,如果你得到了这样的幂,看看生成数组的代码,因为很有可能它做得不对。您应该始终知道您的代码将生成什么样的结果。使用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)
进行搜索会返回"n"个元素。如果没有找到那么多,Nokogiri会使用零值填充它们。
这与我们假设first(n)
所做的相反,因为Enumerable#first
返回up-to-n,并且不会用nil填充。这不是一个错误,但这是一个意外的行为,因为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
试试这个
doc.css(".tweet").first(fetch_number).each do |item|
title = item.css("a")[0]['title'] rescue nil
end
让我知道它是否有效?它不会显示错误
尝试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|
)