无法访问块内的 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].class
是 NilClass
,而不是你报告的 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
,像 search
和 xpath
returns 一个节点集和 text
将连接来自所有 returned 节点的文本,使得很难从各个节点检索文本。而是使用:
doc.css('bar p').map(&:text)
这将 return 包含找到的每个节点的文本的数组:
doc.css('div p').text
# => "foobar"
对比:
doc.css('div p').map(&:text)
# => ["foo", "bar"]
另见“”。
我运行以下成功:
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].class
是 NilClass
,而不是你报告的 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
,像 search
和 xpath
returns 一个节点集和 text
将连接来自所有 returned 节点的文本,使得很难从各个节点检索文本。而是使用:
doc.css('bar p').map(&:text)
这将 return 包含找到的每个节点的文本的数组:
doc.css('div p').text
# => "foobar"
对比:
doc.css('div p').map(&:text)
# => ["foo", "bar"]
另见“