如何将表解析为有意义的块?

时间:2012-02-12 07:31:19

标签: ruby-on-rails ruby xpath screen-scraping nokogiri

我需要在一组页面上提取数据表。我已经可以遍历页面了。

如何提取表格的数据?我正在使用Ruby和Nokogiri,但我认为这是一个相当普遍的问题。

我在the following image中的每一行中加下了所需的数据点。

html的示例是:http://pastebin.com/YYFPbFLC

我如何通过Nokogiri将此表解析为有意义的块?

表的xpath是:

/html/body/table/tbody/tr/td[2]/table/tbody/tr[2]/td/table/tbody/tr/td[2]/table/tbody/tr/td/table/tbody/tr/td[2]/table

该表具有可变数量的数据行和格式化行。我只想收集包含有意义数据的行,但是我没有看到通过XPath区分它的方法,除非第二列可靠地包含“keyword”。每个行都有一个XPath:

1st meaningful row is: /html/body/table/tbody/tr/td[2]/table/tbody/tr[2]/td/table/tbody/tr/td[2]/table/tbody/tr/td/table/tbody/tr/td[2]/table/tbody/tr[2]
...
Last meaningful row: /html/body/table/tbody/tr/td[2]/table/tbody/tr[2]/td/table/tbody/tr/td[2]/table/tbody/tr/td/table/tbody/tr/td[2]/table/tbody/tr[N]

需要匹配“关键字”上文字内容的第一个有意义的列是:

/html/body/table/tbody/tr/td[2]/table/tbody/tr[2]/td/table/tbody/tr/td[2]/table/tbody/tr/td/table/tbody/tr/td[2]/table/tbody/tr[2]/td[2]

第一行数据的最后一列是:

/html/body/table/tbody/tr/td[2]/table/tbody/tr[2]/td/table/tbody/tr/td[2]/table/tbody/tr/td/table/tbody/tr/td[2]/table/tbody/tr[2]/td[6]

每一行都是一条记录,并且该列的时间戳/ td是时间戳中的时间;年,月和日都在各自的变量中,可以附加一个完整的时间戳:

/html/body/table/tbody/tr/td[2]/table/tbody/tr[2]/td/table/tbody/tr/td[2]/table/tbody/tr/td/table/tbody/tr/td[2]/table/tbody/tr[2]/td[5]

1 个答案:

答案 0 :(得分:6)

XPath的第一条规则是:永远不要使用来自Firebug或其他浏览器工具的自动生成的XPath。这会创建脆弱的XPath,将所有页面元素视为同等重要且必需的,甚至是您不关心的部分。例如,如果页面顶部的通知出现并且恰好位于表格中,则可能会导致解析失败。

相反,想一想人类如何识别它。在这种情况下,您需要“标题下的第一个表格,其中包含'今天'字样”。这是XPath:

//table[preceding-sibling::h2[contains(text(), "today")]][1]

这表示采用前面有h2的表格(换句话说,就在h2之后),其中h2包含“今天”这个词。然后拿第一张这样的表。

然后您需要识别您感兴趣的行。请注意,某些行只是包含单个td的分隔符,因此您要确保只解析具有多个td的行标签。在XPath中,即:

//tr[td[2]]

然后你只需抓住所有列的内容。在第一个中,您可以删除“大小”之前的所有内容,以获得唯一的值。把它们放在一起:

doc = Nokogiri::HTML.parse(html)

events = []

doc.xpath('//table[preceding-sibling::h2[contains(text(), "today")]][1]//tr[td[2]]').each do |row|
  cols = row.search('td/text()').map(&:to_s)
  events << {
    :magnitude   => cols[0].gsub(/^.*of magnitude /,''),
    :temp_area   => cols[1],
    :time_start  => cols[2],
    :time_middle => cols[3],
    :time_end    => cols[4]
  }
end

输出结果为:

[
 {:magnitude=>"F1.7",
  :temp_area=>"0",
  :time_start=>"01:11:00",
  :time_middle=>"01:24:00",
  :time_end=>"01:32:00"},
 {:magnitude=>"F3.1",
  :temp_area=>"0",
  :time_start=>"04:01:00",
  :time_middle=>"04:10:00",
  :time_end=>"04:26:00"},
 {:magnitude=>"F3.5",
  :temp_area=>"134F55",
  :time_start=>"06:24:00",
  :time_middle=>"06:42:00",
  :time_end=>"06:53:00"},
 {:magnitude=>"F1.4",
  :temp_area=>"0",
  :time_start=>"11:58:00",
  :time_middle=>"12:06:00",
  :time_end=>"12:16:00"},
 {:magnitude=>"F1.0",
  :temp_area=>"0",
  :time_start=>"13:02:00",
  :time_middle=>"13:05:00",
  :time_end=>"13:09:00"},
 {:magnitude=>"D53.7",
  :temp_area=>"134F55",
  :time_start=>"17:37:00",
  :time_middle=>"18:37:00",
  :time_end=>"18:56:00"}
]