この投稿は 「python Advent Calendar 2017 - Qiita」 の 9日目の記事です。
こんにちは、akiyoko です。
「Python Advent Calendar」は 4年連続 4度目の参加になります。 *1, *2, *3
はじめに
皆さん、CSV は好きですよね? Excel も大好きですね?
じゃあ当然、CSVファイルは Excel で開きますよね。
文字化けは? ・・もちろん嫌いですよね。
でも CSVファイルを Excel で開こうとしたときに、こんな文字化け地獄を経験したことはありませんでしたか? *4
ということで今回は、Excel で直接開いたときに文字化けしない CSV ファイルを Python3 で作成する方法 を紹介したいと思います。(おまけで Python2 でのやり方も書いておきますが、今時 Python2 で消耗している人なんていないですよね? *5)
結論
結論を先に書くと、
- 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)」と呼ぶべきでしょうか。議論の余地はあるものの(*6)、拡張子を「.csv」としておくことでダブルクリック時に自動的に Excel が起動してくれるので(アプリケーションが関連付けられているので)、拡張子は「.csv」とした方がよいでしょう。
検証(Python 3)
ファイルオープン時に「encoding='utf-16'」と指定することで、符号化方式が「UTF-16 LE with BOM」となります。
「encoding='utf-8-sig'」(UTF-8 with BOM)だと、環境によっては(Mac + Excel 2011 とか?)文字化けすることがあるので推奨しません。
import csv def main(): rows = [['髙﨑 將'], ['あああ', 'いいい', 'ううう'], ['Ⅰ・Ⅱ・Ⅲ', '①②③']] # OK with open('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) # これでもOK with open('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) # 文字化けしないが、セルごとに分かれないのでNG with open('utf_16.csv', 'w', newline='', encoding='utf-16') as f: w = csv.writer(f, quoting=csv.QUOTE_ALL) w.writerows(rows) # 文字化け (しない場合もある) with open('utf_8_sig.csv', 'w', newline='', encoding='utf-8-sig') as f: w = csv.writer(f, quoting=csv.QUOTE_ALL) w.writerows(rows) # 文字化け (しない場合もあるが、セルごとに分かれないのでNG) with open('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) # 文字化け with open('utf_8.csv', 'w', newline='', encoding='utf-8') as f: w = csv.writer(f, quoting=csv.QUOTE_ALL) w.writerows(rows) # 文字化け with open('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 class UnicodeWriter: """ 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)() def writerow(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) def writerows(self, rows): for row in rows: self.writerow(row) def main(): rows = [[u'髙﨑 將'], [u'あああ', u'いいい', u'ううう'], [u'Ⅰ・Ⅱ・Ⅲ', u'①②③']] with open('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:《過去記事》akiyoko.hatenablog.jp
*4:ネタが古いですね。図は、「悪循環画像ジェネレータ」を利用させていただきました。
*5:・・はい、私です。