はじめに
CSVファイルを特定の日付の列を使って、別々のファイルにする処理を作ってみました。
作った背景は、CSVファイルをBigQueryへ取り込もうと思ったのですが、BigQueryではシャーディングを使いたくて、そうするとCSV自体が分割してた方がよく、前処理としてCSVを分割したくなったためです。
なお、入力ファイル1行ごとに出力ファイルをオープンするという駄作。誰かのためになればと思い、恥を忍んで公開ですw
改善の余地は大いにありますが、元ファイルは約20万行、出力ファイル数(日付のパターン数)は850個で、約1分でした。Core i7 3.5GHz、メモリ16GBの2012年くらいに作ったデスクトップで、2022年現在ではハイスペックでもないけど普段使いなら悪くもないくらいの環境で。なので1度実行するだけなら別にいいかなと思っています。
ポイント:生のCSVの行のままコピペしたかった
要件的にも技術的にも大したことないプログラムだけど、1点だけ変なことをやってるので、そこだけ書いておきます。
このプログラムは特定の列を取り出して出力ファイルを決定するのですが、取り出すためにはCSVを真面目にパースしないといけない。パースした結果の配列を使って出力すると、ダブルクオーテーションを自分でつけたりして、元データとは内容が変わってしまう。それが嫌だったため、変なことに手を出したという次第です。
その目的のため、同じファイルのwithの入れ子をしてます。生のCSV文字列を読むためのf
→raw_line
と、CSVのデータをパースして特定の列を取り出すためのf_forcsv
→csv_rec
として分けてます。
# ファイルを読んで出力
# 生のファイルを読むファイルポインタと、CSVとして読むポインタを各々開く(もっとスマートな方法がありそう)
header = ''
with open(input_csv_file, mode='r', encoding='utf-8') as f:
with open(input_csv_file, mode='r', encoding='utf-8') as f_forcsv:
csv_reader = csv.reader(f_forcsv)
for i, (raw_line, csv_rec) in enumerate(zip(f, csv_reader)):
if i==0:
header = raw_line
continue
# 日付列の値を取得
split_key = csv_rec[target_column_index]
#print(split_key)
# CSV出力
output_csv(output_file_base, split_key, header, raw_line)
ちなみに、最初は下記のように書いて、おかしな動きになりました。
with open(input_csv_file, mode='r', encoding='utf-8') as f:
csv_reader = csv.reader(f)
for i, (raw_line, csv_rec) in enumerate(zip(f, csv_reader)):
~
for文の1回のループでf
のイテレーターを、f
としてもcsv_reader
としても呼び出し、2回呼び出してしまうためです。
おわりに
「同じ入力ファイルを2つ開く」というダサい点について、スマートな書き方があったら教えてください。
なお「入力ファイル1行ごとに出力ファイルをオープンする」というダサい点について、出力ファイルポインタを全部持っておいて開きっぱなしにして・・・という解決策は、やろうと思えばできるかもとは思ってますが、めんどくさいのでやりませんw