RubyでShift JISやCP932などのCSVをUTF-8に変換して読み込む
結論
おそらくこれだけで十分。詳しい説明や検証結果は後述。
require 'charlock_holmes'
require 'csv'
# CSVファイルのパスを指定
path = 'path/to/file.csv'
# ファイルをすべて読み込んで(大きなファイルは、メモリに優しくない)
# エンコードを推測(あくまで推測)
detection = CharlockHolmes::EncodingDetector.detect(File.read(path))
# => {:type=>:text, :encoding=>"Shift_JIS", :ruby_encoding=>"Shift_JIS", :confidence=>100, :language=>"ja"}
# CharlockHolmes::EncodingDetectorのディテクション結果は、
# CP932であるShift JIS拡張文字コードを含む場合にもShift_JISと見なされてしまう為、CP932を優先して利用する。
# もしそのままShift JISを指定すれば、CSV.foreach()で変換エラーが出る原因となる。
# アップサイドは拡張文字コードを変換できることで、ダウンサイドは7種類の記号文字の見た目が完全に一致しないこと。
encoding = detection[:encoding] == 'Shift_JIS' ? 'CP932' : detection[:encoding]
# CSVを1行ずつUTF-8として読み込む(メモリに優しい!)
CSV.foreach(path,
encoding: "#{encoding}:UTF-8",
headers: true) do |row|
p row.inspect
end
フローとしては、
- ファイル全体を読み込み(メモリを食う方法):
File.read(path)
- 文字コードを判定する
- 文字コードの判定結果の修正:
Shift JIS
拡張であるCP932
を優先する - CSVを1行ずつ読み込む(メモリを食わない方法):
CSV.foreach
説明
大前提
- CharlockHolmesをインストールしてある事
- Ruby 2.2.3で動作を確認済み
利用ファイルの読み込み
require 'charlock_holmes'
require 'csv'
CSVの読み込みと判定
detection = CharlockHolmes::EncodingDetector.detect(File.read(path))
# => "{:type=>:text, :encoding=>\"Shift_JIS\", :ruby_encoding=>\"Shift_JIS\", :confidence=>100, :language=>\"ja\"}"
ここではCharlockHolmesを使ってエンコードを見分けています。(ただし、推測に過ぎない)
File.read(path)
ではファイル全体を読み込む(はず)ため、大容量のファイルを読み込む場合は注意が必要です。Webサイトの場合、通常HTTPリクエストの上限バイト数が設定されていると思いますので、ひとまずこれでよしとしました。
エンコードの修正
encoding = detection[:encoding] == 'Shift_JIS' ? 'CP932' : detection[:encoding]
ここが勘所。
Shift_JISの代わりにCP932をエンコードとして指定する
この後使うCSV.foreach
でCSV.foreach("/path/to/file", encoding: "Shift_JIS:UTF-8")
のようにShift_JIS
を指定すると、含まれる文字コードによってはエラーEncoding::UndefinedConversionError
が出てしまいます。
調べたところCP932
はShift_JIS
から派生した拡張版であるため、CP932
を指定しておくのが無難です。又、以下の参考資料によればShift_JISで作られたファイルがCP932かShift_JISかを文字コードから見分けることは不可能だと思われます。
代わりに、CSV.foreach("/path/to/file", encoding: "CP932:UTF-8")
を使えば問題ないでしょう。
参考資料
- Shift_JIS と Windows-31J (MS932) の違いを整理してみよう
- MySQLのsjisとcp932の違い
- Shift-JISなCSVを読み込む・書き出しするときにエラーを起こさない数少ない方法 (私は'CP932'を使い、モンキーパッチを利用しない方針をとりました)
問題になりそうな文字コードの変換をテストする
尚、私はShift_JISのダメ文字を参考にして「ダメ文字」と呼ばれる文字を含ませたところ、Shift_JIS
を指定した際は想定通りのエラーが出て、CP932
を指定したところ、今のところエラーが確認できておらずUTF-8
で扱うことができています。
ただし、私は文字コードにはそこまで詳しくなく、高精度な変換が求められる場合はより厳密なテストが必要でしょう。
さらに詳しくテストしてみる:Shift_JISとCP932の違い
こちらのテーブルはMySQLのsjisとcp932の違いから引用させていただきました。
以下の7つの文字の見た目が若干異なるようです。下の8161
と8191
はそれなりに見た目が異なりますが、文字コードかどちらか分からない場合はCP932
として読み込んでしまうのは妥協点ではないかと思われます。(そうでなければ、ファイルの提供者にエンコードを教えてもらうか、両方で表示して好きな方を選んで頂く必要がありそうです)
+------+------+-------+
| code | sjis | cp932 |
+------+------+-------+
| 815F | \ | \ |
| 8160 | 〜 | ~ |
| 8161 | ‖ | ∥ |
| 817C | − | - |
| 8191 | ¢ | ¢ |
| 8192 | £ | £ |
| 81CA | ¬ | ¬ |
| 8740 | ? | ① |
| 8741 | ? | ② |
| 8742 | ? | ③ |
| 8743 | ? | ④ |
実際にこのファイルを2通りで保存してみました。(IntelliJ IDEAを使用)
- UTF-8からShift JISに変換して保存した場合、CP932の文字は文字化けします。
- 逆にUTF-8からWindows-31j(CP932)として保存した場合、sjisの文字は文字化けします。
この2つのファイルをCharlockHolmes::EncodingDetector.detect
したところ、どちらもwindows-1250
と検知されます。少しひらがなを追記した程度では結果は変わらず、漢字などを書き足してみたところ、どちらもShift-JIS
と判定されるようになりますが、実際には片方がCP932
なのです。このように文字コードが判定に頼るしかない場合、やはりCP932
と仮定した方が安全です。
CSVを1行ずつ読み込む
CSV.foreach(path,
encoding: "#{encoding}:UTF-8",
headers: true) do |row|
p row.inspect
end
最後になりましたが、ここではCSVを1行ずつ読み込んでいます。
-
path
でファイルへのパスを渡しています -
encoding: "#{encoding}:UTF-8"
では、判定された文字コードをUTF-8に変換しながら読み込むことを指定しています。 -
headers: true
を指定しているので、row[1]
の代わりにrow[:name]
のように扱うことが可能です。処理中に利用するメモリも少なくなるはずです。 -
p row.inspect
でテストとして、コンソールなどにrowの中身を出力しています
参考:Importing Massive CSV Data Into Rails
おまけ
RailsでBULK INSERT
Importing Massive CSV Data Into Railsからの引用ですが、Railsで複数のINSERTクエリをまとめる場合はActiveRecord.import
を利用することでパフォーマンスが向上します。
items = []
CSV.foreach('link/to/file.csv', headers: true) do |row|
items << Item.new(row.to_h)
end
Item.import(items)
こちらについてはQiitaにもわかりやすい記事があります。
→ActiveRecordで複数レコード、BULK INSERTする方法とパフォーマンスについて
CharDet
日本語いっぱいのShift_JISのファイルでも以下のような結果になってしまうので、こちらはあまり実用的ではなさそうです。
detection2 = CharDet.detect(File.read(path))
p detection2.inspect
# => "{\"encoding\"=>\"ISO-8859-2\", \"confidence\"=>0.20525522026784734}"
以上です。
何かお気づきの点がありましたら、是非コメントをお願いします!
Author And Source
この問題について(RubyでShift JISやCP932などのCSVをUTF-8に変換して読み込む), 我々は、より多くの情報をここで見つけました https://qiita.com/daichi87gi/items/9097adfd47d9725097f1著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .