Ruby
CSV

RubyのCSV処理で気をつけるポイント

トレタ Advent Calendar 2017の7日目記事です。
サービスをスタートして4年がたち、初期のころにつくったCSV処理のコードを最近いくつか修正しました。そのポイントについてまとめようと思います。

こんなコードを

CSV.parse(NKF.nkf('-w', params[:file].read)).each do |i|
  process(i)
end

こういうふうにするようにしました。

open(params[:file].tempfile, 'rt:UTF-16:UTF-8', undef: :replace, invalid: :replace, replace: '') do |f|
  CSV.new(f, col_sep: "\t").each do |line|
    process(line)
  end
end

ポイント1: 全データをメモリに載せない

  • CSV.parse/CSV.read/CSV.tableあたりは全データがメモリにのってしまうので使わないようにします。
  • その点ではCSV.foreachもよいですが、ポイント2と3の文字コードや改行コードを考えるとCSV.newのほうが都合がいいです。
  • Rails等でファイルアップロードのデータを受け取るとき場合には、params[:file].tempfileを使えばファイルから読みだすことができて全データがメモリに載るのをさけることができます。
  • ふつうにファイルからデータを読めばいいケースではたんにファイル名を指定します。

ポイント2: 文字コード

  • NKF.nkfはstringを入力にとるのでポイント1を考えるとつかいにくいので、Encodingで変換します。
  • コード上はUTF-16のデータをうけとるようになっていますが、要件に合わせて変更します。この例でUTF-16を使っている理由はポイント4で説明します。
  • NKFと違ってEncodingの場合は変換時にundefやinvalidの例外が起こるでその場合の処理を指定します。(UTF-16からUTF-8であれば必要ないかも)

ポイント3: 改行コード

  • CSVライブラリはカラムのなかに複数の改行コードが混じっているとCSV::MalformedCSVError: Unquoted fields do not allow \r or \nがおきます。
  • これはrow_sepオプションで区切りの改行文字を指定することで回避できますが、そもそも複数種類の改行のデータを持っていいても困ることのほうが多いので、\nに正規化します。
  • Encodingの指定でrtを指定するか、universal_newlineオプションを使うと\nに正規化することができます。

ポイント4: Excelからデータを想定する

  • 残念ながら世の中の表データの大半はExcel取り扱われています。
  • Excelを使っているひとにCSVでくださいというと、たいていの場合Shift_JIS(CP932)のデータきてしまいます。
  • これはExcelでCSV書き出しをするとそうなるからで、UTF-8のCSVでほしい思っていてもなかなかそういうわけにはいきません。
  • また、Shift_JISで書き出されてしまうと、元データにShift_JISの範囲外の文字が入っていた場合にそれが失われていて、再変換したところで元データは手にはいりません。
  • こういうことを避けるために、ExcelではCSVではなく、Unicodeテキストという形式で保存してデータをもらうようにします。
  • UnicodeテキストのデータはUTF-16のTSV(タブ区切り)なので、それを受け取ることを想定して書いておきます。

今回、CSVを書き出すケースについては書いてませんが、同じように気をつけたほうがいい思います。あらかじめこういう点を考慮したコード書くのは手間ですが、あとあと文字コードやメモリ消費量でトラブルになる可能性をを考えると、はじめから少し手間をかけたほうがいいかなと思っています。