为什么我会为每个表行获取空白记录?

时间:2012-01-11 16:09:53

标签: html parsing screen-scraping nokogiri

我有以下代码,感谢另一个SO问题/答案:

page = agent.page.search("table tbody tr").each do |row|
  time        = row.css("td:nth-child(1)").text.strip
  source      = row.css("td:nth-child(2)").text.strip
  destination = row.css("td:nth-child(3)").text.strip
  duration    = row.css("td:nth-child(4)").text.strip
  Call.create!(:time => time, :source => source, :destination => destination, :duration => duration)
end

它运行良好,当我运行rake任务时,它正确地将数据放入我的Rails应用程序中的正确表行,但是,由于某种原因,在成功创建行的记录后,它也创建了一个空白记录。

我无法理解。从代码的外观来看,它在每行中发出create!命令。

您可以在https://gist.github.com/1574942Parse html into Rails without new record every time?看到完整的佣金任务 导致此代码的另一个问题是“{{3}}”。

1 个答案:

答案 0 :(得分:1)

基于评论:

  

我认为你可能是对的,我已经查看了远程网页上的HTML,并且他们正在为每个分配了一个类的表行添加一个包装。我想知道是否有任何方法可以让脚本跳过空行?

如果您看到的HTML结构如下:

<table>
  <tbody>
    <tr>
      <tr>
        <td>time</td>
        <td>source</td>
        <td>destination</td>
        <td>duration</td>
      </tr>
    </tr>
  </tbody>
</table>

然后这将显示问题:

require 'nokogiri'
require 'pp'

html = '<table><tbody><tr><tr><td>time</td><td>source</td><td>destination</td><td>duration</td></tr></tr></tbody></table>'
doc = Nokogiri::HTML(html)
page = doc.search("table tbody tr").each do |row|
  time        = row.css("td:nth-child(1)").text.strip
  source      = row.css("td:nth-child(2)").text.strip
  destination = row.css("td:nth-child(3)").text.strip
  duration    = row.css("td:nth-child(4)").text.strip
  hash = {
    :time        => time,
    :source      => source,
    :destination => destination,
    :duration    => duration 
  }
  pp hash
end

输出:

{:time=>"", :source=>"", :destination=>"", :duration=>""}
{:time=>"time",
 :source=>"source",
 :destination=>"destination",
 :duration=>"duration"}

获取空行的原因是HTML格式不正确。外面<tr>不应该在那里。修复很简单,也可以使用正确的HTML。

此外,内部css访问不太正确,但为什么这样是微妙的。我会谈到的。

要解决第一个问题,我们将添加一个条件测试:

page = doc.search("table tbody tr").each do |row|

变为:

page = doc.search("table tbody tr").each do |row|
  next if (!row.at('td'))

运行后,输出现在是:

{:time=>"time",
 :source=>"source",
 :destination=>"destination",
 :duration=>"duration"}

这就是解决这个问题所需要的全部内容,但是代码中有些东西正在以艰难的方式处理,需要一些“splainin”,但首先是代码更改:

自:

time        = row.css("td:nth-child(1)").text.strip
source      = row.css("td:nth-child(2)").text.strip
destination = row.css("td:nth-child(3)").text.strip
duration    = row.css("td:nth-child(4)").text.strip

更改为:

time, source, destination, duration = row.search('td').map{ |td| td.text.strip }

运行该代码输出您想要的内容:

{:time=>"time",
 :source=>"source",
 :destination=>"destination",
 :duration=>"duration"}

所以事情仍然很糟糕。

以下是原始代码的问题:

csssearch的别名。 Nokogiri为两者返回NodeSet。 text将从空的NodeSet返回一个空字符串,您可以查看查看外部row.css("td:nth-child(...)").text.strip的每个<tr>调用。所以,Nokogiri没有按照你的要求默默地做你想要的事情,因为它在语法上是正确的,并且在逻辑上是正确的,因为你告诉它要做的事情;它只是没能达到你的期望。

使用at或其中一个别名(如css_at)查找第一个匹配的访问者。因此,理论上我们可以继续为每个访问者使用row.at("td:nth-child(1)").text.strip多个赋值,这会立即显示您的HTML存在问题,因为text会被炸毁。但那不够禅宗。

相反,我们可以使用map迭代NodeSet中返回的单元格,让它收集所需的单元格内容并去掉它们,然后对变量进行并行分配:

time, source, destination, duration = row.search('td').map{ |td| td.text.strip }

再次,运行这个:

require 'nokogiri'
require 'pp'

html = '<table><tbody><tr><tr><td>time</td><td>source</td><td>destination</td><td>duration</td></tr></tr></tbody></table>'
doc = Nokogiri::HTML(html)
page = doc.search("table tbody tr").each do |row|
  next if (!row.at('td'))

  time, source, destination, duration = row.search('td').map{ |td| td.text.strip }

  hash = {
    :time        => time,
    :source      => source,
    :destination => destination,
    :duration    => duration 
  }
  pp hash
end

给我:

{:time=>"time",
 :source=>"source",
 :destination=>"destination",
 :duration=>"duration"}

将其改进您的代码并获得:

page = agent.page.search("table tbody tr").each do |row|
  next if (!row.at('td'))
  time, source, destination, duration = row.search('td').map{ |td| td.text.strip }
  Call.create!(:time => time, :source => source, :destination => destination, :duration => duration)
end

您可能不需要page =