尽管有关主题的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
这不起作用 - 现在我得到一个“文件名太长”的错误。
$ 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| ...
这两个似乎都可行。
首先将文件转换为UTF8然后读取它也可以很好地工作:
iconv -f utf-16 -t utf8 testfile.csv | ruby -rcsv -e 'CSV(STDIN).each {|row| puts row}'
Iconv似乎正确理解该文件在开始时有一个BOM并在转换时将其剥离。
在处理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
。