我成功运行以下内容:
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>'
答案 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].class
是NilClass
,而不是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
,如search
和xpath
返回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”。