如何使用Nokogiri解析普通的HTML表格?

5
我想使用Nokogiri来解析一个HTML页面。页面的一部分中有一个表格,但没有任何特定的ID。能否提取出如下内容:
Today,3,455,34
Today,1,1300,3664
Today,10,100000,3444,
Yesterday,3454,5656,3
Yesterday,3545,1000,10
Yesterday,3411,36223,15

从这个HTML代码:

<div id="__DailyStat__">
  <table>
    <tr class="blh"><th colspan="3">Today</th><th class="r" colspan="3">Yesterday</th></tr>
    <tr class="blh"><th>Qnty</th><th>Size</th><th>Length</th><th class="r">Length</th><th class="r">Size</th><th class="r">Qnty</th></tr>
    <tr class="blr">
      <td>3</td>
      <td>455</td>
      <td>34</td>
      <td class="r">3454</td>
      <td class="r">5656</td>
      <td class="r">3</td>
    </tr>

    <tr class="bla">
      <td>1</td>
      <td>1300</td>
      <td>3664</td>
      <td class="r">3545</td>
      <td class="r">1000</td>
      <td class="r">10</td>
    </tr>

    <tr class="blr">
      <td>10</td>
      <td>100000</td>
      <td>3444</td>
      <td class="r">3411</td>
      <td class="r">36223</td>
      <td class="r">15</td>
    </tr>
  </table>
</div>
1个回答

10

作为一个快速且简单的第一步,我会这样做:

html = <<EOT
<div id="__DailyStat__">
  <table>
    <tr class="blh"><th colspan="3">Today</th><th class="r" colspan="3">Yesterday</th></tr>
    <tr class="blh"><th>Qnty</th><th>Size</th><th>Length</th><th class="r">Length</th><th class="r">Size</th><th class="r">Qnty</th></tr>
    <tr class="blr">
      <td>3</td>
      <td>455</td>
      <td>34</td>
      <td class="r">3454</td>
      <td class="r">5656</td>
      <td class="r">3</td>
    </tr>

    <tr class="bla">
      <td>1</td>
      <td>1300</td>
      <td>3664</td>
      <td class="r">3545</td>
      <td class="r">1000</td>
      <td class="r">10</td>
    </tr>

    <tr class="blr">
      <td>10</td>
      <td>100000</td>
      <td>3444</td>
      <td class="r">3411</td>
      <td class="r">36223</td>
      <td class="r">15</td>
    </tr>
  </table>
</div>
EOT

#    Today              Yesterday
#    Qnty Size   Length Length Size  Qnty
#    3    455    34     3454   5656  3
#    1    1300   3664   3545   1000  10
#    10   100000 3444   3411   36223 15


require 'nokogiri'

doc = Nokogiri::HTML(html)

使用 CSS 查找表格的开头,并定义一些位置来存储我们要捕获的数据:

table = doc.at('div#__DailyStat__ table')

today_data     = []
yesterday_data = []

遍历表格中的行,跳过标题行:

table.search('tr').each do |tr|

  next if (tr['class'] == 'blh')

初始化数组以捕获每行相关数据,有选择地将数据推入适当的数组:

  today_td_data     = [ 'Today'     ]
  yesterday_td_data = [ 'Yesterday' ]

  tr.search('td').each do |td|
    if (td['class'] == 'r')
      yesterday_td_data << td.text.to_i
    else
      today_td_data << td.text.to_i
    end
  end

  today_data     << today_td_data
  yesterday_data << yesterday_td_data

end

并输出数据:

puts today_data.map{ |a| a.join(',') }
puts yesterday_data.map{ |a| a.join(',') }

> Today,3,455,34
> Today,1,1300,3664
> Today,10,100000,3444
> Yesterday,3454,5656,3
> Yesterday,3545,1000,10
> Yesterday,3411,36223,15

为帮助您更好地理解正在发生的事情,在“tr”循环退出时,today_datayesterday_data数组是类似于数组的数组:

[["Today", 3, 455, 34], ["Today", 1, 1300, 3664], ["Today", 10, 100000, 3444]]

或者,不用循环 "td" 标签并感知标签的类,我可以获取“tr”的内容,然后使用 scan 获取数字,并将结果数组切片为“today”和“yesterday”数组:

  tr_data = tr.text.scan(/\d+/).map{ |i| i.to_i }

  today_td_data     = [ 'Today',     *tr_data[0, 3] ]
  yesterday_td_data = [ 'Yesterday', *tr_data[3, 3] ]

在实际开发中,例如在工作中,我会使用这种方式而不是我最初写的方式,因为它更简洁。

请注意,我没有使用XPath。在Nokogiri中使用XPath并完成此操作也很容易,但为了简单起见,我更喜欢使用CSS选择器。XPath可以访问单个"td"标记内容,但它也会开始看起来像线噪声,这是我们编写代码时要避免的,因为它会影响维护。我也可以使用CSS来钻取到正确的"td"标记,如'tr td.r',但我认为这并不能改进代码,只是另一种方法。


感谢@the-tin-man。它正常工作,只是我必须将doc.at更改为doc.css,因为出现了一些错误。我只有一点小问题,我会尝试解决。我没有足够的声望来投票,所以加油 :) - JraNil
at 可以与 CSS 和 XPath 一起使用,但只返回节点的第一个出现位置,因此您可能会有多个 table 标签。 csssearch 的别名,它返回 NodeSet,又称为 Nodes 数组,因此您必须对结果进行索引或迭代。at_css 是 CSS 特定版本的 at - the Tin Man
@JraNil,另外,作为FYI,该代码可以与您的示例HTML一起使用,因此示例中必须有某些内容缺失。如果您生成准确的示例,我们可以提供更好的答案。 - the Tin Man
是的,原始网页似乎存在一些问题。当我使用open-uri或wget调用它时,它没有响应。我正在尝试创建一个代理并使用Mechanize来调用该页面。 - JraNil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接