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