2013-12-21 35 views
4

儘管主題上有很多SO線程,但我在解析CSV時遇到了問題。這是從AdWords關鍵字規劃工具下載的.csv文件。以前,Adwords可以選擇將數據導出爲「普通CSV」(可以使用Ruby CSV庫進行分析),現在選項可以是Adwords CSV或Excel CSV。這兩種格式導致此問題(通過一個終端會話所示):使用不同的編碼和庫解析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

你能提供一個你正試圖解析的文件的例子嗎? – benjaminjosephw

+0

你能不能把'file.read'放在沒有異常的地方? –

+0

@benjaminjosephw下面是我正在使用的確切文件:http://jamesabbottdd.com/examples/testfile.csv – abbottjam

回答

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建議文件編碼每隔1位小尾數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由於某種原因在返回單個字節時遇到BOM時called in CSV,導致無效的UTF-16。

你可以得到CSV剝去BOM中,通過使用bom|utf-16-le作爲編碼:

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

您可能希望將字符串轉換爲更熟悉的編碼代替,在這種情況下,你可以這樣做:

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

這兩個這些似乎工作正常。

+0

不僅有專注,而且非常有教育意義。頂尖工作 - 謝謝! – abbottjam

+0

這對我有用 - 似乎Tableau(上帝幫助他們)以UTF-16le吐出CSV,製表符分隔......只是。嘆。 – snowking16

2

轉換文件爲utf8,然後再閱讀它也工作得很好:

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

語言Iconv似乎正常,該文件在開始有一個BOM和轉換時剝離它關閉理解。