如何将HTML数据导出到CSV文件

时间:2016-04-28 10:00:39

标签: ruby nokogiri

我正在尝试从此HTML中删除并制作CSV文件:

<ul class="object-props">
                <li class="object-props-item price">
                    <strong>CHF 14&#39;800.-</strong>
                </li>
                <li class="object-props-item milage">31&#39;000 km</li>
                <li class="object-props-item date">08.2012</li>
            </ul>

我想用以下方法提取价格和里程:

require 'rubygems'
require 'nokogiri'
require 'CSV'
require 'open-uri'

url= "/tto.htm"
data = Nokogiri::HTML(open(url))

CSV.open('csv.csv', 'wb') do |csv|
  csv << %w[ price mileage ]

  price=data.css('.price').text
  mileage=data.css('.mileage').text 

  csv << [price, mileage]
end

结果并不是我所期待的。创建了两列,但是如何删除CHF和KM等字符以及为什么里程数不显示result

2 个答案:

答案 0 :(得分:0)

我的猜测是HTML中的文字包含度量单位; CHF以瑞士法郎为代价,km以里程为公里。

您可以添加split.firstsplit.last来获取没有度量单位的数字,例如:

2.3.0 :007 > 'CHF  100'.split.last
 => "100"
2.3.0 :008 > '99 km'.split.first
 => "99"

答案 1 :(得分:0)

删除/忽略不需要的文本不是Nokogiri问题,它是字符串处理问题:

require 'nokogiri'

doc = Nokogiri::HTML(<<EOT)   
li class="object-props-item price"
<strong>CHF 14&#39;900.-</strong>
<li class="object-props-item milage">61&#39;000 km</li>
EOT

str = doc.at('strong').text # => "CHF 14'900.-"

此时str包含<strong>节点的文本。

一个简单的正则表达式将被提取,这是获取数据的直接方式:

str[/[\d']+/] # => "14'900"

sub可用于删除'CHF '子字符串:

str.sub('CHF ', '') # => "14'900.-"

delete可用于删除字符CHF

str.delete('CHF ') # => "14'900.-"

tr可用于删除非0 .. 9'.-的所有内容:

str.tr("^0-9'.-", '') # => "14'900.-"

如果您不想要'.-,请修改上述其中一项。

  

为什么里程数据不显示

因为CSS选择器与实际的class参数不匹配:

require 'nokogiri'

doc = Nokogiri::HTML('<li class="object-props-item milage">61&#39;000 km</li>')

doc.at('.mileage').text  # => 
# ~> NoMethodError
# ~> undefined method `text' for nil:NilClass
# ~>
# ~> /var/folders/yb/whn8dwns6rl92jswry5cz87dsgk2n1/T/seeing_is_believing_temp_dir20160428-96035-1dajnql/program.rb:5:in `<main>'

相反它应该是:

doc.css('.milage').text # => "61'000 km"

但这并非全是错误的。有一个微妙的问题等着你以后咬你。

csssearch返回一个NodeSet,而atat_css返回一个元素:

doc.css('.milage').class # => Nokogiri::XML::NodeSet
doc.at('.milage').class # => Nokogiri::XML::Element

以下是text传递包含多个匹配节点的NodeSet时会发生什么:

doc = Nokogiri::HTML('<p>foo</p><p>bar</p>')

doc.search('p').class # => Nokogiri::XML::NodeSet
doc.search('p').text # => "foobar"

doc.at('p').class # => Nokogiri::XML::Element
doc.at('p').text # => "foo"

text与NodeSet一起使用时,它会返回连接成单个字符串的所有节点的文本。这可能使文本从一个节点与另一个节点分离变得非常困难。相反,使用at或其中一个at_*等效项从单个节点获取文本。如果要单独从每个节点中提取文本并使用数组:

doc.search('p').map(&:text) # => ["foo", "bar"]

参见&#34; How to avoid joining all text from Nodes when scraping&#34;还

最后,请注意您的HTML示例无效:

doc = Nokogiri::HTML(<<EOT)
li class="object-props-item price"
<strong>CHF 14&#39;900.-</strong>
<li class="object-props-item milage">61&#39;000 km</li>')
EOT

puts doc.to_html

# >> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
# >> <html><body>
# >> <p>li class="object-props-item price"
# >> <strong>CHF 14'900.-</strong>
# >> </p>
# >> <li class="object-props-item milage">61'000 km</li>')
# >> </body></html>

以下是发生的事情:

doc = Nokogiri::HTML(<<EOT)
li class="object-props-item price"
<strong>CHF 14&#39;900.-</strong>
<li class="object-props-item milage">61&#39;000 km</li>')
EOT

doc.at('.price') # => nil

Nokogiri必须做一个修复才能理解第一行,所以它将它包含在<p>中。通过这样做,.price类不再存在,因此您的代码将再次失败。

修复标记会产生正确的响应:

doc = Nokogiri::HTML(<<EOT)
<li class="object-props-item price">
<strong>CHF 14&#39;900.-</strong>
</li>
<li class="object-props-item milage">61&#39;000 km</li>')
EOT
doc.at('.price').to_html # => "<li class=\"object-props-item price\">\n<strong>CHF 14'900.-</strong>\n</li>"

这就是为什么确保您的输入有效非常重要。如果没有它,试图复制你的问题是很困难的。