使用不同的编码和库解析CSV文件

时间:2013-12-21 10:18:43

标签: ruby parsing csv google-adwords

尽管主题上有很多SO主题,但我在解析CSV方面遇到了麻烦。这是从Adwords关键字规划师下载的.csv文件。以前,Adwords可以选择将数据导出为“普通CSV”(可以使用Ruby CSV库进行解析),现在选项可以是Adwords CSV或Excel CSV。这些格式中的两个都会导致此问题(由终端会话说明):

file = File.open('public/uploads/testfile.csv')
 => #<File:public/uploads/testfile.csv> 

file.read.encoding
 => #<Encoding:UTF-8> 

require 'csv'
 => true 

CSV.foreach(file) { |row| puts row }
ArgumentError: invalid byte sequence in UTF-8

让我们改变编码,看看是否有帮助:

file.close
 => nil 

file = File.open("public/uploads/testfile.csv", "r:ISO-8859-1")
 => #<File:public/uploads/testfile.csv> 

file.read.encoding 
=> #<Encoding:ISO-8859-1> 

CSV.foreach(file) { |row| puts row }
ArgumentError: invalid byte sequence in UTF-8

让我们尝试使用其他CSV库:

require 'smarter_csv'
 => true 

file.close
 => nil 

file = SmarterCSV.process('public/uploads/testfile.csv')
ArgumentError: invalid byte sequence in UTF-8

这是一个不赢的局面吗?我是否必须滚动自己的CSV解析器?

我正在使用Ruby 1.9.3p374。谢谢!

更新1:

使用评论中的建议,这是当前版本:

file_contents = File.open("public/uploads/new-format/testfile-adwords.csv", 'rb').read

require 'iconv' unless String.method_defined?(:encode)
if String.method_defined?(:encode)
  file_contents.encode!('UTF-16', 'UTF-8', :invalid => :replace, :replace => '')
  file_contents.encode!('UTF-8', 'UTF-16')
else
  ic = Iconv.new('UTF-8', 'UTF-8//IGNORE')
  file_contents = ic.iconv(file_contents)
end

file_contents.gsub!(/\0/, '') #needed because otherwise, I get "string contains null byte (ArgumentError)"

CSV.foreach(file_contents, :headers => true, :header_converters => :symbol) do |row|
  puts row
end

这不起作用 - 现在我收到“文件名太长”的错误。

3 个答案:

答案 0 :(得分:17)

查看file in question

 $ curl -s http://jamesabbottdd.com/examples/testfile.csv | xxd | head -n3
0000000: fffe 4300 6100 6d00 7000 6100 6900 6700  ..C.a.m.p.a.i.g.
0000010: 6e00 0900 4300 7500 7200 7200 6500 6e00  n...C.u.r.r.e.n.
0000020: 6300 7900 0900 4200 7500 6400 6700 6500  c.y...B.u.d.g.e.

byte order markffee at the start表示文件编码是小端UTF-16,而每个其他位置的00字节都支持这种情况。

这表明你应该能够做到这一点:

CSV.foreach('./testfile.csv', :encoding => 'utf-16le') do |row| ...

然而,这让我invalid byte sequence in UTF-16LE (ArgumentError)来自inside the CSV library。我认为这是由于IO#gets仅在called in CSV遇到BOM时因某种原因返回单个字节,导致UTF-16无效。

使用bom|utf-16-le作为编码,您可以获取CSV以剥离BOM:

CSV.foreach('./testfile.csv', :encoding => 'bom|utf-16le') do |row| ...

您可能更喜欢将字符串转换为更熟悉的编码,在这种情况下您可以执行以下操作:

CSV.foreach('./testfile.csv', :encoding => 'utf-16le:utf-8') do |row| ...

这两个似乎都可行。

答案 1 :(得分:2)

首先将文件转换为UTF8然后读取它也可以很好地运行:

iconv -f utf-16 -t utf8 testfile.csv | ruby -rcsv -e 'CSV(STDIN).each {|row| puts row}'

Iconv似乎正确理解该文件在开始时有一个BOM,并在转换时将其剥离。

答案 2 :(得分:0)

在处理AdWords关键字规划师下载时,有两件事需要解决。一个就是编码。

$ file Keyword\ Stats\ 2019-02-12\ at\ 19_04_53.csv
Keyword Stats 2019-02-12 at 19_04_53.csv: Little-endian UTF-16 Unicode text, with very long lines

分隔符是制表符而不是逗号的事实!

因此,遍历CSV文件非常简单:

CSV.foreach('Keyword Stats 2019-02-12 at 19_04_53.csv', col_sep: "\t", encoding: 'utf-16le:utf-8') do |row|
  puts row
end

仅供参考:\t必须用双引号引起来,因此它将被解释为制表符,而不是字符串\t