由于另一个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!
命令。
您可以在上查看完整的rake任务https://gist.github.com/1574942和导致这段代码的另一个问题是"每次都在没有新记录的情况下将html解析到Rails中?"。
基于注释:
我想你可能是对的,我已经查看了远程网页上的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返回一个空字符串,您将为查看外部<tr>
的每个row.css("td:nth-child(...)").text.strip
调用获得该字符串。所以,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 =
。