无法访问块内的 Nokogiri 元素

Cannot access Nokogiri element within block

我运行以下成功:

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:NilClass.

的 NoMethodError

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

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

link = row.css('td')[4].class

我得到一个没有错误的散列,说 link 的值是 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>'

关键的洞察力是,在第一种情况下,own_table.css('tr') returns a 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.

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

考虑一下:

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

如果您的目的是找到所有匹配项,请将上述选择器中的 #bar 替换为 div,这将放松搜索。

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

doc.css('bar p').text

css,像 searchxpath returns 一个节点集和 text 将连接来自所有 returned 节点的文本,使得很难从各个节点检索文本。而是使用:

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

这将 return 包含找到的每个节点的文本的数组:

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

对比:

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

另见“”。