我有以下代码,感谢另一个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/1574942和Parse html into Rails without new record every time?看到完整的佣金任务 导致此代码的另一个问题是“{{3}}”。
答案 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"}
所以事情仍然很糟糕。
以下是原始代码的问题:
css
是search
的别名。 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 =
。