如何搜索单个节点,而不是所有节点

时间:2016-12-30 20:01:35

标签: ruby xpath nokogiri

我使用XPath选择器选择页面上的每个项目(大约24个),然后我在每个项目上使用XPath选择器返回每个项目的值。

即使我在子节点上运行XPath选择器,它似乎正在搜索所有子节点,我只想在每个子节点上单独完成。

以下代码搜索doc上的每个项目,然后迭代每个html_listing。然后将其传递给get_field_data_from

def get_listing(doc,field_data = {})
  doc.xpath(get_listing_tag[:path]).each do |html_listing|
    fd = get_field_data_from(html_listing,field_data)
    if !field_data &&  fd.detect {|_,data| !data }
      set_uri doc.xpath(get_sub_page_tag[:path])
      get
      fd = get_listing(Nokogiri::HTML(body),fd)
    end
    yield fd
  end
end

所以它遍历所有Fields我正在寻找哪个用于检索包含字符串的XPath选择器

selector = send("get_%s_tag" % field)

如果选择器存在且尚未找到数据,它将使用HTML item上的XPath选择器,使用

存储文本
res[field] = item.xpath(selector[:path]).inner_text

然后返回在下一次迭代中使用的结果哈希。

def get_field_data_from(item,data)
  Fields.inject(data) do |res,field|
    selector = send("get_%s_tag" % field)
    unless !selector || res[field]
      begin
        res[field] = item.xpath(selector[:path]).inner_text
      rescue Exception => e
        puts "Error for field: %s" % field
        raise e
      end
    end
    res
  end
end

似乎在做什么

res[field] = item.xpath(selector[:path]).inner_text

它似乎搜索了所有项目,而不仅仅是搜索项目列表。我知道这样做是因为:

  1. 这样做的:

    puts item.xpath(selector[:path]).inner_text
    

    返回多个结果

  2. 我实际上并没有遍历所有的html_listings。如果它在yield fd中生成字段数据get_listing,我会执行break,因此它只执行一次。

  3. 我似乎无法弄清楚发生了什么。别人看到了吗?

2 个答案:

答案 0 :(得分:1)

您需要在元素上锚定XPath查询:

  • node.xpath("//example")进行全局搜索
  • node.xpath(".//example")执行从当前节点开始的本地搜索

注意前导点.,它将查询锚定在当前节点上。否则,即使您从当前节点调用查询,也会针对根节点运行查询。

如果您按标签名称搜索,请考虑使用CSS选择器。他们比XPath有更少的陷阱。 CSS总是从当前节点进行搜索。

答案 1 :(得分:1)

还有另一个同样严重的问题。

item.xpath(selector[:path]).inner_text

xpath返回一个NodeSet。 inner_text将连接NodeSet中所有节点的结果,从而产生一个通常不会成为你想要的字符串。

require 'nokogiri'

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

doc.search('p').class # => Nokogiri::XML::NodeSet
doc.search('p').inner_text # => "foobar"

相反,您需要使用map来遍历节点列表,然后获取文本:

doc.search('p').map(&:inner_text) # => ["foo", "bar"]

或者,为简单起见:

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

参见&#34; How to avoid joining all text from Nodes when scraping&#34;还