2016-09-28 25 views
0

我运行中访问引入nokogiri元件成功执行以下操作:无法块

require 'nokogiri' 
require 'open-uri' 

own = Nokogiri::HTML(open('https://www.sec.gov/cgi-bin/own-disp?action=getowner&CIK=0001513362')) 
own_table = own.css('table#transaction-report') 

p own_table.css('tr').css('td')[4].css('a').attr('href').value 

=> “/Archives/edgar/data/0001513362/000162828016019444/0001628280-16-019444-index.htm”

然而,当我尝试在一个块使用该元件的上方(如图下面的代码),我得到零一个NoMethodError:NilClass。

我很困惑,因为我认为,在该块中的局部变量的链接将是同一个对象在上面的代码。

此外,如果我改变链接的定义下面:

链路= row.css( 'TD')[4]的.class

我得到无差错的散列,说的值链接是Nokogiri :: XML :: Element。

谁能解释一下,为什么我有一个引入nokogiri :: XML :: Element对象,但不能在其上运行的CSS方法。特别是当我可以在第一个片段中运行它时?

require 'nokogiri' 
require 'open-uri' 

own = Nokogiri::HTML(open('https://www.sec.gov/cgi-bin/own-disp?action=getowner&CIK=0001513362')) 
own_table = own.css('table#transaction-report') 


own_table.css('tr').each do |row| 
    names = [:acq, :transaction_date, :execution_date, :issuer, :form, :transaction_type, :direct_or_indirect_ownership, :number_of_securities_transacted, :number_of_securities_owned, :line_number, :issuer_cik, :security_name, :url] 
    values = row.css('td').map(&:text) 
    link = row.css('td')[4].css('a').attr('href').value 
    values << link 
    hash = Hash[names.zip values] 
    puts hash 
end 

secown.rb:11:in `block in <main>': undefined method `css' for nil:NilClass (NoMethodError) 
    from /Users/piperwarrior/.rvm/gems/ruby-2.2.1/gems/nokogiri-1.6.7.2/lib/nokogiri/xml/node_set.rb:187:in `block in each' 
    from /Users/piperwarrior/.rvm/gems/ruby-2.2.1/gems/nokogiri-1.6.7.2/lib/nokogiri/xml/node_set.rb:186:in `upto' 
    from /Users/piperwarrior/.rvm/gems/ruby-2.2.1/gems/nokogiri-1.6.7.2/lib/nokogiri/xml/node_set.rb:186:in `each' 
    from secown.rb:8:in `<main>' 
+1

请阅读“[mcve]”。当询问代码问题时,我们需要最小输入数据(在这种情况下为HTML),它可以证明问题本身的问题。不要让我们去一个网站阅读整个页面,因为它会减慢我们对您的响应时间,并影响我们帮助他人的能力。您绝不应该使用'css'或'search'链接每个标签。取而代之的是使用更复杂的选择器,从地标到地标跳转到标记中的目标。这不太脆弱。另外,在选择答案之前,您应该等待更长的时间。 –

回答

1

关键见解是,在第一种情况下,own_table.css('tr')返回一个NodeSet.css('td')查找所有td即后裔在该节点集的任何节点,然后查找第四一个(发言作为程序员,第五用于正常人:P)。

第二个片段将每一行分别对待为Node,然后找到所有的后裔td,然后选取第四个。

所以,如果你有这样的结构:

tr id=1 
    td id=2 
    td id=3 
tr id=4 
    td id=5 
    td id=6 
    td id=7 
    td id=8 
    td id=9 

那么第一个片段会给你的ID 7 TD(这是在所有TR第四TD);第二个片段会去找第四td在ID 1 TR,那么第四td在ID 4 TR,但它的错误,因为ID 1 TR没有第四个TD。

编辑:具体来说,在检查你的URL,第一tr没有td;所有其他有12所以own_table.css('tr')[0].css('td')[4].classNilClass,不Nokogiri::XML::Element你汇报。

+0

我的意思是改变块中的局部变量链接link = row.css('td')[4] .class给出Nokogiri :: XML :: Element – PiperWarrior

+1

我知道你的意思。它适用于80行(带12'td'的行);不是第一个(没有'td'的)。 – Amadan

+1

@Amadan是对的,第一行有'th'而不是'td',这是因为你得到'NoMethodError' values = row.css('td')。map(&:text) => [] ' – retgoat

1

考虑一下:

require 'nokogiri' 

doc = Nokogiri::HTML(<<EOT) 
<html> 
    <body> 
    <div><span><p>foo</p></span></div> 
    <div id="bar"><span><p>bar</p></span></div> 
    </body> 
</html> 
EOT 

如果我连锁我要去寻找<div>的内线所有匹配<p>节点的方法:

doc.css('div').css('span').css('p').to_html 
# => "<p>foo</p><p>bar</p>" 

或:

doc.css('div').css('p').to_html 
# => "<p>foo</p><p>bar</p>" 

这相当于使用下面的选择器,只是它们更高效一些安永不涉及调用的libxml多次:

doc.css('div span p').to_html 
# => "<p>foo</p><p>bar</p>" 

或:

doc.css('div p').to_html 
# => "<p>foo</p><p>bar</p>" 

真的,你应该找到目标标记的地标,并从一个跨越到下一个,而不是从标签步骤标记:

doc.css('#bar p').to_html 
# => "<p>bar</p>" 

如果你的目的是要找到所有匹配然后替换#bardiv在上面的选择,它就会松开搜索。

最后,如果你的目标是提取一组节点的文字,你不希望使用这样的:

doc.css('bar p').text 

css,像searchxpath返回一个节点集和text将串联来自所有返回节点的文本,使得难以从各个节点检索文本。改用:

doc.css('bar p').map(&:text) 

这将返回一个包含每个节点的发现的文本的数组:

doc.css('div p').text 
# => "foobar" 

对:

doc.css('div p').map(&:text) 
# => ["foo", "bar"] 

见 “How to avoid joining all text from Nodes when scraping” 也。

+0

此外,在上面的代码中,doc.css('div').css('span').css('p').to_html不能像指定的那样工作(它返回一个空字符串)。如果你删除,css('span'),它会。 – PiperWarrior

+0

非常有启发性的帖子。代码看起来更干净,并且在摆脱链接时不会重复。 – PiperWarrior

+0

'如果删除,css('span')'...那是因为我忘了将示例更新为带有'span'标签的版本。我会更新它。 –