f◯ck EXCEL
検証環境
以下の環境で検証しました。
mac
macOS Mojave
プロセッサ 2.7 GHz Intel Core i5
メモリ 16 GB
Excel for Mac バージョン 15.25.1
windows
Windows 10 Home
Intel Core i5-8250U 1.6GHz
メモリ 8GB
Excel for Office 365 MSO(16.0.11629.20210)
CSV出力
結論から言うと以下のポイントがあります。
- 文字コードはBOM付きUTF-8、改行コードは
\r\n
(CRLF)にする-
BOM付きUTF-8は
髙
や〜
などShift-JISやCP932(Shift-JIS拡張)に存在しない文字の出力のため - CRLFはWindowsのExcelで改行させるため
-
BOM付きUTF-8は
- 項目をダブルクォートでくくる
- 改行やクォートが崩れるので古いバージョン(2016年10月より前)のExcelでは編集しない
CSVを編集する環境
DBデータのCSVエクスポート機能が欲しいと言われた場合、文字コードや改行コードなど特に指定がなくとも、エンジニアでも無ければ一般的にはwindowsマシンのExcelで開くことを想定していると思います。
ちなみにDBはutf8mb4
前提です。
文字コード
Excelは何も指定しなければShift-JIS
でファイルを開こうとするので、何も考えずにUTF-8
のままCSV出力すると文字化けしてしまいます。Shift-JIS
、もしくはShift-JIS
を拡張したCP932
として出力することも考えられますが、UTF-8
の文字列をShift-JIS
等に変換する場合、そこそこの確率でその文字コードには存在しない文字に遭遇します。
代表的なもので髙
や﨑
(Shift-JIS)、〜
(CP932)等がそれぞれ存在しない文字になります。ちなみに〜
(0x301C
)と~
(0xFF5E
)は別の文字です。[1]
改行コード
windowsなのでCRLF
で出力します。
コード
DBの店舗一覧をCSV出力してみます。
ActiveRecord::Base
を継承したStore
という店舗モデルを想定しています。
collection = Store.all
headers = collection.column_names
csv_str = CSV.generate(liberal_parsing: true,
converters: nil,
col_sep: ',',
quote_char: '"') do |csv|
csv << headers
collection.pluck(headers).each do |columns|
csv << columns
end
end
file_path = '/Users/kurashita/Desktop/output.csv'
File.open(file_path, 'w', encoding: Encoding::UTF_8) do |file|
file.write "\xEF\xBB\xBF"
# BOM付きUTF-8、改行コードCRLF
# 置き換え出来ない場合は〓に変換
file.write NKF.nkf("-w8 -Lw --fb-subchar=0x3013", csv_str)
end
読み込み
こちらも以下のポイントがあります。
- 文字コードは事前に指定できるなら指定
- 指定できない場合は
NKF.guess
で推測- 文字列読み込みは20MBで0.8秒、200MBで8秒程度
- 改行コードは
\n
(LF)に正規化- DB登録が前提のため。それ以外にも
-
File.open
、CSV.new
を併用して読み込む-
CSV.table
やCSV.foreach
は便利だが全データがメモリに展開される/エンコード等オプション指定の関係でCSV.new
を使う - 文字コードが決まっていてファイルサイズが大きくなければ
CSV.table
で
-
コード
上記ポイントを抑えたコードが以下になります。
file_path = '/Users/kurashita/Desktop/input.csv'
# 200MBまで読み込む
file_encoding = NKF.guess File.read(file_path, 200_000_000)
csv_file_options = {
universal_newline: true, # 改行コードの正規化
undef: :replace,
invalid: :replace,
replace: ''
}
File.open(file_path, "rt:BOM|#{file_encoding}", universal_newline: true,
undef: :replace,
invalid: :replace,
replace: ''
) do |f|
CSV.new(f, liberal_parsing: true,
headers: true,
converters: nil,
header_converters: :symbol,
skip_lines: /^(?:,\s*)+$/,
col_sep: ',',
quote_char: '"').each do |csv|
# ...
end
end
注意点
2016年10月より前のバージョンのExcelでの編集は避けることをおすすめします。
理由としては
- デフォルトでタブ区切りとして保存する
- カンマ区切りをUTF-8で保存が出来ない
- CSVはデフォルトでカンマ区切りとして読み込みする
というものがあり、BOM付きUTF-8なファイルを編集して保存するとUTF-8でタブ区切りになるか、Shift-JISのカンマ区切りになるため、正しく項目を扱えなかったり、変換できない文字が_
になったりします。
どうしても古いエクセルしかなくてそれで開きたいという場合は、[データ]タブ → [テキストファイル(テキストからデータを取り込み)]でファイルを読み込むことでウィザードに従ってUTF-8やタブ区切りなファイルを読み込むことも出来ますが、ダブルクォートが取れていて改行が正しく認識できなかったり、そもそも面倒な開き方だと思うのでやはり避けるのが無難です。
参考記事
https://qiita.com/swdyh/items/884a0cade2e5fcebfd8b
https://sites.google.com/site/fudist/Home/grep/damemoji