尽管主题上有很多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
这不起作用 - 现在我收到“文件名太长”的错误。
答案 0 :(得分:17)
$ 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
。