2014-10-02 88 views
0

我正在使用Nokogiri来屏幕抓取网站的内容。在前10位中查找?

我设置了fetch_number来指定我想检索的<divs>的数量。例如,我可能需要first(10)来自目标页面的推文。

的代码看起来是这样的:

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

但是,当小于10个匹配div标签返回,它将报告

NoMethodError: undefined method 'css' for nil:NilClass 

这是因为,当没有匹配的HTML被发现,它将返回零。

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

UPDATE:

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 

返回resultes(接近端):

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个元件,但它retuns 30阵列。 它实际上不是一个数组,但是Nokogiri::XML::NodeSet?我不确定。

回答

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的参数。

这意味着你的循环遍历值永远不会返回零,并且你不会有返回的数组需要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只返回一个节点,什么节点样子。

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返回最多为n,并且不会填充nils。这是不是一个错误,但它是意外的行为,因为可枚举的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 
+0

非常感谢。我希望我能同时接受2个答案。很有帮助! – cqcn1991 2014-10-03 13:39:03

+0

真正的问题是这样的。我想获得一页20个项目。所以我用'first(20)'编写一个选择器。但是,它可能只有15个项目。所以剩余的20个阵列将有15个项目+ 5个零。我不觉得这可以通过使用更好的选择器来改进,而是将“第一(20)”更改为更合适的方式。但我不知道如何。 – cqcn1991 2014-10-03 13:50:18

+1

'[] .first(2)#=> []'。除非你不正确地处理数组,否则你不能得到“15 + 5无”,你只能得到15。这是基于很多经验处理网站。所以,问题不在于你如何请求20,而是你在做什么。 – 2014-10-03 16:41:03

1

试试这个

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

让我知道它的工作原理或不?它不会显示错误

+1

HMM或只是'标题= item.css( “A”)[0] [ '标题']如果item' – mhutter 2014-10-02 10:18:36

+0

@Manuel我认为招应该在'first'方法.... – cqcn1991 2014-10-02 10:20:55

+0

是的,对不起,我只是中省略你的答案的第一个和最后一行在我的评论。当然,我的路线应该介入他们之间。 – mhutter 2014-10-02 10:23:46

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|

+1

使用'compact'是一个bandaid来修补真正的问题,它没有使用适当的选择器。修复选择器和nils将消失,消除使用'compact'的需要。 – 2014-10-02 23:58:52