【问题标题】:How to parse consecutive tags with Nokogiri?如何用 Nokogiri 解析连续的标签?
【发布时间】:2011-12-13 00:09:01
【问题描述】:

我有这样的 HTML 代码:

<div id="first">
<dt>Label1</dt>
<dd>Value1</dd>

<dt>Label2</dt>
<dd>Value2</dd>

...
</div>

我的代码不起作用。

doc.css("first").each do |item|
  label = item.css("dt")
  value = item.css("dd")
end

首先显示所有&lt;dt&gt; 标签,然后是&lt;dd&gt; 标签,我需要“label: value”

【问题讨论】:

    标签: ruby nokogiri


    【解决方案1】:

    首先,您的 HTML 应该在 &lt;dl&gt; 中包含 &lt;dt&gt;&lt;dd&gt; 元素:

    <div id="first">
        <dl>
            <dt>Label1</dt>
            <dd>Value1</dd>
            <dt>Label2</dt>
            <dd>Value2</dd>
            ...
        </dl>
    </div>
    

    但这不会改变您解析它的方式。您想找到&lt;dt&gt;s 并对其进行迭代,然后在每个&lt;dt&gt; 处,您可以使用next_element 获取&lt;dd&gt;;像这样:

    doc = Nokogiri::HTML('<div id="first"><dl>...')
    doc.css('#first').search('dt').each do |node|
        puts "#{node.text}: #{node.next_element.text}"
    end
    

    只要结构与您的示例匹配,这应该可以工作。

    【讨论】:

    • 而不是doc.css('#first').search('dt').each 为什么不只是doc.css('#first dt').each?另请注意,此答案在假设每个 &lt;dt&gt; 之后总是只有一个 &lt;dd&gt; (这在一般 HTML 中可能不是这种情况)的假设下有效。
    • @Phrogz:.css.search 没有充分的理由,也许它更接近 OP 已经拥有的东西。而且我确实包含了“只要结构与您的示例匹配就应该有效”的警告。我同意您的方法在一般情况下会更好。 (这只是我上一条评论的拼写更正,因为我不知道拼写)
    【解决方案2】:

    假设某些&lt;dt&gt; 可能有多个&lt;dd&gt;,您希望找到所有&lt;dt&gt;,然后(对于每个)在下一个&lt;dt&gt; 之前找到以下&lt;dd&gt;。这在纯 Ruby 中很容易做到,但在 XPath 中更有趣。 ;)

    鉴于此设置:

    require 'nokogiri'   
    html = '<dl id="first">
      <dt>Label1</dt><dd>Value1</dd>
      <dt>Label2</dt><dd>Value2</dd>
      <dt>Label3</dt><dd>Value3a</dd><dd>Value3b</dd>
      <dt>Label4</dt><dd>Value4</dd>
    </dl>'    
    doc = Nokogiri.HTML(html)
    

    不使用 XPath

    doc.css('dt').each do |dt|
      dds = []
      n = dt.next_element
      begin
        dds << n
        n = n.next_element
      end while n && n.name=='dd'
      p [dt.text,dds.map(&:text)]
    end
    #=> ["Label1", ["Value1"]]
    #=> ["Label2", ["Value2"]]
    #=> ["Label3", ["Value3a", "Value3b"]]
    #=> ["Label4", ["Value4"]]
    

    使用一点 XPath

    doc.css('dt').each do |dt|
      dds = dt.xpath('following-sibling::*').chunk{ |n| n.name }.first.last
      p [dt.text,dds.map(&:text)]
    end
    #=> ["Label1", ["Value1"]]
    #=> ["Label2", ["Value2"]]
    #=> ["Label3", ["Value3a", "Value3b"]]
    #=> ["Label4", ["Value4"]]
    

    使用很多 XPath

    doc.css('dt').each do |dt|
      ct = dt.xpath('count(following-sibling::dt)')
      dds = dt.xpath("following-sibling::dd[count(following-sibling::dt)=#{ct}]")
      p [dt.text,dds.map(&:text)]
    end
    #=> ["Label1", ["Value1"]]
    #=> ["Label2", ["Value2"]]
    #=> ["Label3", ["Value3a", "Value3b"]]
    #=> ["Label4", ["Value4"]]
    

    【讨论】:

      【解决方案3】:

      在查看其他答案后,这是一种低效的做法。

      require 'nokogiri'
      a = Nokogiri::HTML('<div id="first"><dt>Label1</dt><dd>Value1</dd><dt>Label2</dt><dd>Value2</dd></div>')
      
      dt = []
      dd = []
      
      a.css("#first").each do |item|
        item.css("dt").each {|t| dt << t.text}
        item.css("dd").each {|t| dd << t.text}
      end
      
      dt.each_index do |i|
        puts dt[i] + ': ' + dd[i]
      end
      

      在css中要引用ID,需要在前面加上#号。对于一个类,它是 .象征。

      【讨论】:

      • 请注意,由于"#first" 只能匹配一个元素,因此您拥有的内容等同于(但更糟):item = a.at_css("#first")。在外面使用each 完全是多余的。
      • 另外,请注意,此答案假定&lt;dt&gt;&lt;dd&gt; 之间始终存在一对一的配对。尽管这对于原始问号标记是正确的,但在现实世界的标记中可能并不总是正确的。最后,在迭代两个配对数组时,您可以考虑使用dt.zip(dd).each{ |dt,dd| ... } 而不是each_with_index
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多