使用Nokogiri解析HTML并使用最近的&#34; <div>&#34; </div>获取文本

时间:2014-10-08 15:51:36

标签: html ruby nokogiri

我的HTML包含:

<div class = "s">
   <p> text1 </p>
   <div class = "i">
      <p> text2 </p>
   </div>
</div> 

我需要使用距离<div>最近的"s"获取所有文字。

例如,我试图获得:

array = []
html.css("s").each do |element|
  array << element.text.strip
end

除了在我的阵列中出现"text2"之外,这一切都很好,我不想要那些。因此,对于"text2",最接近的<div>"i"类,我不想在我的数组中看到它。

我该如何解决这个问题?可以有不同的类名和更深的嵌套,例如:

<div class = "s">
   <p> text1 </p>
   <div class = "i">
      <p> text2 </p>
      <div class = "s">
         <p> text3 </p>
         <div class = "p">
           <p> text4 </p>
         </div>
      </div> 
   </div>
</div> 

从这里,我想得到一个数组:["text1", "text3"]

3 个答案:

答案 0 :(得分:3)

更新:更好的仅xpath答案。我原来的答案如下。

# Given a Nokogiri::HTML document in the `html` variable:
html.xpath("//text()[normalize-space() and ancestor::div[1][@class='s']]").map(&:text).map(&:strip)

这只是找到所有非空白文本节点,其最近的div祖先的类别为s。它与我原来的答案是一样的,除了它完全是在XPath中完成的。

<div class = "s">
   <p> text1 </p>
   <div class = "i">
      <p> text2 </p>
   </div>
</div>
# => ["text1"]

<div class = "s">
   <p> text1 </p>
   <div class = "i">
      <p> text2 </p>
      <div class = "s">
         <p> text3 </p>
         <div class = "p">
           <p> text4 </p>
         </div>
      </div>
   </div>
</div>
# => ["text1", "text3"]

<div class = "s">
  <div class='p'>
    text 1
  </div>
  text 2
</div>
# => ["text 2"]

原始答案:

html.search("//div[@class='s']//text()").
  select {|t| t.ancestors("div").first.attr("class") == "s" }.
  map(&:text).join.squeeze.strip
# => "text1"

这里的基本思想是我们找到所有来自div.s的文本节点,然后为每个文本节点找到最近的div祖先,并且只接受具有最近div祖先的节点等级s

它有点CPU密集型,但它符合严格的要求。

答案 1 :(得分:1)

你可以这样做:

html.css('.s > p').map {|node| node.text.strip }

答案 2 :(得分:0)

我从这开始:

require 'nokogiri'

doc = Nokogiri::XML(<<EOT, &:noblanks)
<div class = "s">
   <p> text1 </p>
   <div class = "i">
      <p> text2 </p>
      <div class = "s">
         <p> text3 </p>
         <div class = "p">
           <p> text4 </p>
         </div>
      </div> 
   </div>
</div> 
EOT

doc.search('.s').map{ |div| div.child.text.strip } 
# => ["text1", "text3"]

由于HTML格式化,我认为很难找到合适的节点child节点的".s"是包含“\ n”的下一个Text节点。忽略它们很困难,因为它们可能不是文本,它们可能是您想要返回的节点。

诀窍是告诉Nokogiri在解析文档时剥离空白节点,这有效地压平了HTML,删除了所有缩进,使得可以相信目标之后的下一个节点是想要的。 / p>


  foob​​ar会导致这种技术失败。

是的,它会,并且需要额外的逻辑来清除那些:

require 'nokogiri'

doc = Nokogiri::XML(<<EOT, &:noblanks)
<div class = "s">
   <p> text1 </p>
   <div class = "i">
      <p> text2 </p>
      <div class = "s">
         <p> text3 </p>
         <div class = "p">
           <p> text4 </p>
         </div>
      </div> 
      <div class='s'><div class='i'>foobar</div></div>
   </div>
</div> 
EOT

这是旧的逻辑:

doc.search('.s').map{ |div| div.child.text.strip } 
# => ["text1", "text3", "foobar"]

快速测试以清除不需要的内容:

doc.search('.s').reject{ |div| div.child['class'] == 'i' }.map{ |div| div.child.text.strip } 
# => ["text1", "text3"]