Ruby无法解析CSV文件:CSV :: MalformedCSVError(第1行引号使用不当)。

41

Ubuntu 12.04 LTS

Ruby ruby 1.9.3dev (2011-09-23 revision 33323) [i686-linux]

Rails 3.2.9

以下是我收到的CSV文件内容:

"date/time","settlement id","type","order id","sku","description","quantity","marketplace","fulfillment","order city","order state","order postal","product sales","shipping credits","gift wrap credits","promotional rebates","sales tax collected","selling fees","fba fees","other transaction fees","other","total"
"Mar 1, 2013 12:03:54 AM PST","5481545091","Order","108-0938567-7009852","ALS2GL36LED","Solar Two Directional 36 Bright White LED Security Flood Light with Motion Activated Sensor","1","amazon.com","Amazon","Pasadena","CA","91104-1056","43.00","3.25","0","-3.25","0","-6.45","-3.75","0","0","32.80"

然而,当我试图解析CSV文件时,我遇到了错误:

1.9.3dev :016 > options = { col_sep: ",", quote_char:'"' }
=> {:col_sep=>",", :quote_char=>"\""} 

1.9.3dev :022 > CSV.foreach("/tmp/my_data.csv", options) { |row| puts row }
CSV::MalformedCSVError: Illegal quoting in line 1.
    from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1925:in `block (2 levels) in shift'
    from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1887:in `each'
    from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1887:in `block in shift'
    from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1849:in `loop'
    from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1849:in `shift'
    from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1791:in `each'
    from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1208:in `block in foreach'
    from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1354:in `open'
    from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1207:in `foreach'
    from (irb):22
    from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/bin/irb:16:in `<main>'

然后我尝试简化数据,即:

"name","age","email"
"jignesh","30","jignesh@example.com"

然而我仍然遇到相同的错误:

      1.9.3dev :023 > CSV.foreach("/tmp/my_data.csv", options) { |row| puts row }
  CSV::MalformedCSVError: Illegal quoting in line 1.
      from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1925:in `block (2 levels) in shift'
      from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1887:in `each'
      from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1887:in `block in shift'
      from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1849:in `loop'
      from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1849:in `shift'
      from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1791:in `each'
      from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1208:in `block in foreach'
      from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1354:in `open'
      from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/lib/ruby/1.9.1/csv.rb:1207:in `foreach'
      from (irb):23
      from /home/jigneshgohel/.rvm/rubies/ruby-1.9.3-rc1/bin/irb:16:in `<main>'

我再次尝试将数据简化如下:

name,age,email
jignesh,30,jignesh@example.com

它有效。请查看下面的输出:

  1.9.3dev :024 > CSV.foreach("/tmp/my_data.csv") { |row| puts row }
  name
  age
  email
  jignesh
  30
  jignesh@example.com
   => nil 

但我将会收到含有引号数据的CSV文件,所以删除引号不是我真正想要的解决方案。我无法弄清楚是什么原因导致了之前例子中的CSV::MalformedCSVError: Illegal quoting in line 1.

我已通过在文本编辑器中启用“显示空格字符”和“显示行结尾”来验证CSV中没有前导/尾随空格。同时,我已使用以下内容验证了编码。

  1.9.3dev :026 > File.open("/tmp/my_data.csv").read.encoding
  => #<Encoding:UTF-8> 

注意:我也尝试使用CSV.read,但是遇到了同样的错误。

请问有人能帮我解决这个问题并让我理解出错的原因吗?

=====================

我刚在http://www.ruby-forum.com/topic/448070找到了以下帖子,并尝试了以下内容:

  file_data = file.read
  file_data.gsub!('"', "'")
  arr_of_arrs = CSV.parse(file_data)

  arr_of_arrs.each do |arr|
    Rails.logger.debug "=======#{arr}"
  end

并得到以下输出:

   =======["\xEF\xBB\xBF'date/time'", "'settlement id'", "'type'", "'order id'", "'sku'", "'description'", "'quantity'", "'marketplace'", "'fulfillment'", "'order city'", "'order state'", "'order postal'", "'product sales'", "'shipping credits'", "'gift wrap credits'", "'promotional rebates'", "'sales tax collected'", "'selling fees'", "'fba fees'", "'other transaction fees'", "'other'", "'total'"]
    =======["'Mar 1", " 2013 12:03:54 AM PST'", "'5481545091'", "'Order'", "'108-0938567-7009852'", "'ALS2GL36LED'", "'Solar Two Directional 36 Bright White LED Security Flood Light with Motion Activated Sensor'", "'1'", "'amazon.com'", "'Amazon'", "'Pasadena'", "'CA'", "'91104-1056'", "'43.00'", "'3.25'", "'0'", "'-3.25'", "'0'", "'-6.45'", "'-3.75'", "'0'", "'0'", "'32.80'"]

由于默认使用逗号字符作为分隔符,导致读取数据出现问题。然而,我尝试使用如下所示的quote_char选项:

因为默认使用逗号字符作为col_sep(列分隔符),这使得正确读取数据变得混乱。不过,我尝试使用类似于以下方式的quote_char选项:

  arr_of_arrs = CSV.parse(file_data, :quote_char => "'")

但结果出现了以下错误:

   CSV::MalformedCSVError (Illegal quoting in line 1.):

谢谢,Jignesh


1
使用您提供的示例数据,解析工作正常。没有收到任何“CSV :: MalformedCSVError:第1行中的非法引用”错误。 - Anand Shah
在我编辑的部分,输出包含以下内容: "\xEF\xBB\xBF'日期/时间'"。这会引起问题吗?我不知道它代表什么。谢谢。 - Jignesh Gohel
5
文件开头的Unicode字符是BOM(字节顺序标记)。您可以尝试使用sub!(/^\xEF\xBB\xBF/,'')CSV.foreach("test.csv", encoding: "bom|utf-8") - Anand Shah
谢谢Anand,我会尝试使用你提出的编码解决方案。与此同时,在我的临时解决方案中,当我使用header_converters时,如下所示:arr_of_arrs = CSV.parse(file_data, { col_sep: ";", headers: true, header_converters: [ :symbol ] }) 我遇到了以下错误:Encoding::UndefinedConversionError ("\xEF" from ASCII-8BIT to UTF-8)。它提到了ASCCII-8BIT作为编码方式。这种编码方式有什么影响?那些BOM字符是怎么进去的?这样的错误应该清楚地显示在库抛出的异常中,而不是在to_s输出中偶然发现。 - Jignesh Gohel
3
下面的链接http://joelonsoftware.com/articles/Unicode.html将有助于理解编码的重要性。至于那些BOM字符是怎么进去的,你需要检查接收到的CSV文件的来源以及保存方式。 - Anand Shah
@Anand 使用了你的建议:csv_options[:encoding] = "bom|utf-8"; CSV.foreach(uploaded_file.path, csv_options) do |row| ... end; 这里的 csv_options 是一个 Hash,包含了 CSV.new 支持的选项。 - Jignesh Gohel
11个回答

35
quote_chars = %w(" | ~ ^ & *)
begin
  @report = CSV.read(csv_file, headers: :first_row, quote_char: quote_chars.shift)
rescue CSV::MalformedCSVError
  quote_chars.empty? ? raise : retry 
end

虽然不是完美的,但大多数时候都有效。

N.B. CSV.parseCSV.read 拥有相同的参数,因此可以使用文件或内存中的数据。


25

感谢Anand提供的编码建议,这为我解决了非法引用问题。

注意:如果您想让迭代器跳过标题行,请添加headers: :first_row,如下所示:

CSV.foreach("test.csv", encoding: "bom|utf-8", headers: :first_row)

4
“encoding: "bom|utf-8"”是解决我的问题的方法。 - Flavio Wuensche
4
如果在 Ruby 2.4+ 中遇到 ArgumentError: unknown encoding name - bom|utf-8 错误,请确保将 csv gem 更新到版本 3 或更高版本(在 Gemfile 文件中添加 gem 'csv', '~> 3.0')。 - Andreas

14

我刚刚遇到了类似的问题,发现 CSV 不喜欢在分隔符和引号字符之间有空格。一旦我去掉了这些空格,一切都好了。所以我原来的代码是:

12,  "N",  12, "Pacific/Majuro"

但是一旦我使用 gsub 删去空格后,

.gsub(/,\s+\"/,',\"')

导致

12,"N",  12,"Pacific/Majuro"

一切顺利。


1
请注意,如果您想在逗号值内替换带引号字符串两侧的空格... gsub(/,\s+"/,',"').gsub(/"\s+,/,'",')。 - bjm88
这是我第二次遇到这个问题,也是第二次找到了这个答案。不幸的是,我不能再次点赞它。 - DickieBoy

13

太棒了@mArtinko5MB!liberal_parsing: "如果设置为true,则CSV会尝试解析与RFC 4180不符合的输入,例如未引用字段中的双引号。" - stwr667

5

这个线程中传递选项:quote_char => "|"

CSV.read(filename, :quote_char => "|")


这似乎也适用于CSV.foreach方法。 - bananaforscale
它可以正常工作,但与 headers: true 或 headers: :first_row 一起使用时会导致列值混乱。 - mArtinko5MB

4
:liberal_parsing => true 参数添加到 CSV.read 中,这应该可以解决一些“非法引用”的问题。

2

我遇到了一个商标字符的问题,导致出现了错误。

商标字符在UTF-8中被翻译为"!",所以是开放式引号符号引起了错误。因此我做了这个:

.gsub!("\"!", "")

然后我尝试创建我的CSV对象,它正常工作了。


0

我在一行代码中遇到了这个问题:Agricover 22040169 Access; TonnoSport

问题在于CSV解析器预期会出现",以完全包围逗号分隔的文本。

解决方案是使用引号代替",我确信这不会出现在我的记录中:

CSV.parse(file_path, headers: true, :quote_char => "|")

想了解更多信息吗?

https://ruby-doc.org/stdlib-2.6.1/libdoc/csv/rdoc/CSV.html#class-CSV-label-Reading


0

我尝试读取文件并获取字符串,然后将该字符串解析为CSV表格,但是出现了异常:

CSV.read(File.read('file.csv'), headers: true)
CSV::MalformedCSVError: Unclosed quoted field on line 1794.

这里提供的所有答案都没有对我起作用。事实上,投票最高的那个需要解析的时间太长了,最终我终止了执行。它很可能引发了许多异常,而在大文件上进行此操作的时间是昂贵的。

更加棘手的是,错误信息并不是很有帮助,因为它是一个大型CSV文件。第1794行指的是哪里?我使用LibreOffice打开了文件,没有遇到任何问题。第1794行是csv文件的最后一行数据。因此,问题显然与CSV文件的末尾有关。我决定将其作为字符串使用File.read来检查内容。我注意到该字符串以回车符结束:

,\"\"\r

我决定使用chomp并删除文件末尾的回车符。请注意,如果$/没有从默认的Ruby记录分隔符更改,则chomp还会删除回车符字符(即它将删除\n、\r和\r\n)。
CSV.parse(File.read('file.csv' ).chomp, headers: true)
 => #<CSV::Table mode:col_or_row row_count:1794>

它起作用了。问题在于文件末尾的 \r 字符。


-1
这个错误的不太常见的原因是文件没有进行任何字段引用,但是仍然设置了quote_char(默认为"),并且一个或多个字段恰好包含该字符。
要完全禁用字段引用,请在解析选项中设置quote_char: nil
例如,给定一个像这样的文件/tmp/people.csv:
Actor,Dwayne "The Rock" Johnson,1972-05-02
Character,TV's Frank,1956-08-30

可以用这个进行解析:

CSV.read('/tmp/people.csv', quote_char: nil)

作为一个快速的跟进,使用 liberal_parsing: true 也是可行的。或许这是两种选项中更“安全”的方法,因为它应该允许在将来使用 " 引用值。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接