この投稿は 「python Advent Calendar 2017 - Qiita」 の 9日目の記事です。
こんにちは、akiyoko です。
「Python Advent Calendar」は 2年ぶり 3度目の参加になります。 *1, *2
はじめに
皆さん、CSV は好きですよね? Excel も大好きですね?
じゃあ当然、CSVファイルは Excel で開きますよね。
文字化けは? ・・もちろん嫌いですよね。
でも CSVファイルを Excel で開こうとしたときに、こんな文字化け地獄を経験したことはありませんでしたか? *3
ということで今回は、Excel で直接開いたときに文字化けしない CSV ファイルを Python3 で作成する方法をご紹介したいと思います。(おまけで Python2 でのやり方も書いておきますが、今時 Python2 で消耗している人なんていないですよね? *4)
結論
結論を先に書くと、
- Unicode の文字符号化方式は 「UTF-16(正確には、BOMありの UTF-16 LE)」
- タブ区切り
で CSVファイルを作成すれば、Excel で直接開いても文字化けせず、それぞれの値がセルごとに分かれて表示されます。
(参考)Which encoding opens CSV files correctly with Excel on both Mac and Windows? - Stack Overflow
Windowsでは、リトルエンディアンのUTF-16符号化スキームが使われている。内部表現では16ビット符号なし整数を符号単位とするUTF-16符号化形式(CEFなのでBOMはなし)として扱い、ファイルなどではBOMありのUTF-16符号化スキーム(リトルエンディアン)が主である。
Note Microsoft uses UTF-16, little endian byte order.
とあるように、Microsoft Excel が 「BOMありの UTF-16 LE」を扱っているため、この方法がベストと言えそうです。
なお、「CSV(Comma-Separated Values)」と言いながらも区切り文字がタブなので、厳密には「TSV(Tab-Separated Values)」と呼ぶべきでしょうか。議論の余地はあるものの(*5)、拡張子を「.csv」としておくことでダブルクリック時に自動的に Excel が起動してくれるので(アプリケーションが関連付けられているので)、拡張子は「.csv」とした方がよいでしょう。
検証(Python 3)
ファイルオープン時に「encoding='utf-16'」と指定することで、符号化方式が「UTF-16 LE with BOM」となります。
「encoding='utf-8-sig'」(UTF-8 with BOM)だと、環境によっては文字化けすることがあるので、
import csv defmain(): rows = [['髙﨑 將'], ['あああ', 'いいい', 'ううう'], ['Ⅰ・Ⅱ・Ⅲ', '①②③']] # OKwithopen('utf_16_excel_tab.csv', 'w', newline='', encoding='utf-16') as f: w = csv.writer(f, dialect='excel-tab', quoting=csv.QUOTE_ALL) w.writerows(rows) # これでもOKwithopen('utf_16_excel_tab_2.csv', 'w', newline='', encoding='utf-16') as f: w = csv.writer(f, dialect='excel', delimiter='\t', quoting=csv.QUOTE_ALL) w.writerows(rows) # 文字化けしないが、セルごとに分かれないのでNGwithopen('utf_16.csv', 'w', newline='', encoding='utf-16') as f: w = csv.writer(f, quoting=csv.QUOTE_ALL) w.writerows(rows) # 文字化け (しない場合もある)withopen('utf_8_sig.csv', 'w', newline='', encoding='utf-8-sig') as f: w = csv.writer(f, quoting=csv.QUOTE_ALL) w.writerows(rows) # 文字化け (しない場合もあるが、セルごとに分かれないのでNG)withopen('utf_8_sig_excel_tab.csv', 'w', newline='', encoding='utf-8-sig') as f: w = csv.writer(f, dialect='excel-tab', quoting=csv.QUOTE_ALL) w.writerows(rows) # 文字化けwithopen('utf_8.csv', 'w', newline='', encoding='utf-8') as f: w = csv.writer(f, quoting=csv.QUOTE_ALL) w.writerows(rows) # 文字化けwithopen('utf_8_excel_tab.csv', 'w', newline='', encoding='utf-8') as f: w = csv.writer(f, dialect='excel-tab', quoting=csv.QUOTE_ALL) w.writerows(rows) if __name__ == '__main__': main()
OK
セルごとに分かれない
文字化け
なお、確認した環境は、
- macOS 10.12.16 + Microsoft Office 365 & Excel for Mac 2011
- Windows 10 + Microsoft Office 2010
です。
ちなみに、open 時に「newline=''」を指定している理由は、Windows 対策のためです。
(参考)
- python - How to write UTF-8 in a CSV file - Stack Overflow
- Pythonでcsvファイルにデータを書き込みをする基本中の基本
- CSV file written with Python has blank lines between each row - Stack Overflow
おまけ(Python 2)
# -*- coding: utf-8 -*-import cStringIO import codecs import unicodecsv as csv classUnicodeWriter: """ A CSV writer which will write rows to CSV file "f", which is encoded in the given encoding."""def__init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds): # Redirect output to a queue self.queue = cStringIO.StringIO() self.writer = csv.writer(self.queue, dialect=dialect, **kwds) self.stream = f self.encoder = codecs.getincrementalencoder(encoding)() defwriterow(self, row): self.writer.writerow([s.encode("utf-8") for s in row]) # Fetch UTF-8 output from the queue ... data = self.queue.getvalue() data = data.decode("utf-8") # ... and reencode it into the target encoding data = self.encoder.encode(data) # write to the target stream self.stream.write(data) # empty queue self.queue.truncate(0) defwriterows(self, rows): for row in rows: self.writerow(row) defmain(): rows = [[u'髙﨑 將'], [u'あああ', u'いいい', u'ううう'], [u'Ⅰ・Ⅱ・Ⅲ', u'①②③']] withopen('test_unicode_writer.csv', 'w') as f: w = UnicodeWriter(f, dialect=csv.excel_tab, encoding='utf-16') w.writerows(rows) if __name__ == '__main__': main()
https://docs.python.org/2/library/csv.html#examplesの UnicodeWriter をそのまま使えばいいよという話ですが、それにしても面倒臭いですよね。いっそ滅んでしまえばいいのに、Python2。
まとめ
Excel で直接開いても文字化けしない CSVファイルを Python3 で作成するには、
- ファイルオープン時に「encoding='utf-16'」と指定
- csv.writer の引数に「dialect='excel-tab'」と指定
とするのがスマートで確実です。
今回はちょっとレガシーな話題でした。
明日は、drillerさんの「python Advent Calendar 2017 - Qiita」 10日目の記事です。
よろしくお願いします。
*1:《過去記事》akiyoko.hatenablog.jp
*2:《過去記事》akiyoko.hatenablog.jp
*3:ネタが古いですね。図は、「悪循環画像ジェネレータ」を利用させていただきました。
*4:・・はい、私です。