无法访问块内的Nokogiri元素

时间:2016-09-28 02:05:17

标签: ruby 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”

但是,当我尝试在块中使用上面的元素时(如下面的代码所示),我得到nil的NoMethodError:NilClass。

我很困惑,因为我认为块中的局部变量链接与上面的代码中的对象相同。

此外,如果我将下面链接的定义更改为:

link = 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>'

2 个答案:

答案 0 :(得分: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);第二个片段会尝试在id 1 tr中找到第四个td,然后在id 4 tr中找到第四个td,但它会出错,因为id 1 tr没有第四个td。< / p>

修改:具体来说,检查过您的网址后,第一个tr没有td;所有其他人都有12.所以own_table.css('tr')[0].css('td')[4].classNilClass,而不是Nokogiri::XML::Element

答案 1 :(得分: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

如果我链接方法,我将在<p> s中找到所有匹配的<div>节点:

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>"

如果您打算查找所有匹配项,请在上面的选择器中将#bar替换为div,它将放松搜索。

最后,如果你的目标是提取一组节点的文本,你不想使用类似的东西:

doc.css('bar p').text

css,如searchxpath返回NodeSet,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”。