【Python】サブフォルダを含めてフォルダ内を走査→ファイルリストをCSV書き出し


初めての投稿になります。
最近Pythonの勉強を始め、中途半端な知識のままですが備忘録的に投稿していきたいと思います。
何かアドバイスなど気軽にコメントもらえれば幸甚です。

やりたいこと

会社環境で部門の共有フォルダを使用しているのだが、容量が小さく、すぐに一杯になってしまう。
このフォルダにおいてどんなファイルが容量を占めてしまっているのか可視化したい。
会社ルール的にフリーソフト使用が原則NGなので、勉強がてら自分でツールを作ってみた。

勉強したこと

1.walkモジュールでディレクトリ走査

os.walkで簡単にフォルダ内の走査を行なうことができる。
(VBAの場合は再帰処理を行なう必要があったのが、walkを使うことで簡単に書ける)

使い方

for curDir, dirs, files in os.walk(target_folder):
上記でtarget_folder_pに記したフォルダを対象として、ファイル群をfilesに、サブフォルダ群をdirsに、それぞれが格納されているフォルダをcurDirに格納する。
その後、そのforの中でさらにforネストしてfilesやdirsを展開し、処理を行なっていく。

使用例:

import os

target_folder = r"C:\Users\aaa\Desktop\写真フォルダ"

for curDir, dirs, files in os.walk(target_folder): 
    for a_file in files:
        print(f"{a_file}はフォルダ:{curDir}に格納されています。")

結果

みかん.jpg はフォルダ:C:\Users\aaa\Desktop\写真フォルダ に格納されています。
りんご.jpg はフォルダ:C:\Users\aaa\Desktop\写真フォルダ に格納されています。
バナナ.jpg はフォルダ:C:\Users\aaa\Desktop\写真フォルダ に格納されています。
犬.jpg はフォルダ:C:\Users\aaa\Desktop\写真フォルダ\動物 に格納されています。
猫.jpg はフォルダ:C:\Users\aaa\Desktop\写真フォルダ\動物 に格納されています。

2.csvファイルへの出力

csvへの出力はopen関数でファイルを開き、csv.writerでcsvに変換して、writerowやwriterowsで書き込んでいく。

書き方の例

import csv

with open出力するファイル名フルパス ,読み込みモード, newline="") as fo:
    CSV_file = csv.writer(fo)
    CSV_file.writerow(["A1", "B1", "C1", "D1"])
    CSV_file.writerow([["A2", "B2", "C2", "D2"],["A3", "B3", "C3", "D3"]])
    CSV_file.writerows([["A4", "B4", "C4", "D4"],["A5", "B5", "C5", "D5"]])

結果:

writerowは1行分書き込み
writerowsは複数行をまとめて書き込み

読み込みモード(open関数の第二引数)

  • "w":書き出し専用(既存のファイルがあった場合、中身が消去されて書き込まれる)
  • "a":追加書き出し(既存のファイルがあった場合、末尾にデータが書き込まれる)
  • "x":新規書き出しのみ(既存のファイルがあった場合、何も書き込まれない)
  • "r":読み込み専用
  • "r+":読み書き両用

オプションのnewlineはまだよく分かっておらず勉強中。ただ、これを省略してしまうと出力したときにデータが一行飛ばしになってしまう。

成果物

ファイルリスト書き出し.py
import os
import pathlib
import csv
from tkinter import filedialog

# 読み込むフォルダを指定する
F_path = os.path.dirname(__file__)
target_folder = filedialog.askdirectory(initialdir=F_path)
target_folder_path = pathlib.Path(target_folder)

filedata = []

# フォルダ内を操作
for curDir, dirs, files in os.walk(target_folder_path):
    for a_File in files:

        file_size = os.path.getsize(os.path.join(curDir, a_File))
        file_ext = os.path.splitext(a_File)

        filedata.append([len(filedata)+1, a_File,file_ext[1], file_size,os.path.join(curDir, a_File)])

# 読み込み結果を表示
print("No.  ファイル名          拡張子          ファイルサイズ(Byte)          フルパス")

for i in range(len(filedata)):
    print(f"{filedata[i][0]}     {filedata[i][1]}     {filedata[i][2]}     {filedata[i][3]}     {filedata[i][4]}")

# ファイル出力先の確認
print("読み込んだフォルダの最上階層にCSVでファイル一覧を出力します。\n出力するファイル名を入力してください(拡張子は不要)")
print("(出力しない場合は空白のままENTERする)")
csv_name = input("出力ファイル名:")

if csv_name == "":
    print("ファイルを出力しませんでした。")

else:
    # 読み込み結果をファイルに出力
    output_csv_path = os.path.join(target_folder_path, csv_name + ".csv")

    with open(output_csv_path, "w", newline="") as fo:
        CSV_file = csv.writer(fo)
        CSV_file.writerow(["No.", "ファイル名","拡張子", "ファイルサイズ(Byte)","フルパス"])
        CSV_file.writerows(filedata)

    print(f"次のファイルを出力しました。=> {output_csv_path}")

結果

No.  ファイル名          拡張子          ファイルサイズ(Byte)          フルパス
1     きのこたけのこ戦争.jpg        .jpg           118437          C:\Users\aaa\Desktop\写真フォルダ\きのこたけのこ戦争.jpg
2     たけのこの里.jpg        .jpg           93003          C:\Users\aaa\Desktop\写真フォルダ\たけのこの里.jpg
3     猫1.jpg        .jpg           31115          C:\Users\aaa\Desktop\写真フォルダ\動物\猫1.jpg
4     猫2.jpg        .jpg           21160          C:\Users\aaa\Desktop\写真フォルダ\動物\猫2.jpg
読み込んだフォルダの最上階層にCSVでファイル一覧を出力します。
出力するファイル名を入力してください(拡張子は不要)
(出力しない場合は空白のままENTERする)
出力ファイル名:output
次のファイルを出力しました。=> C:\Users\aaa\Desktop\写真フォルダ\output.csv

その他の備忘録・メモ

  • 読み込むフォルダはtkinter.filedialog.askdirectoryで自分で指定できるように。オプションのinitialdirで本ファイルの場所を参照するようにした。
  • 読み込んだフォルダをpathlib.PathでPath型に変換しているが、別に変換しなくても出力できるっぽい。作成したとき、なぜPath型に変換していたか失念。
  • 読み込んだデータをすべてリスト(配列)にスタックして、まとめてcsvに出力している。VBAの配列と扱いが異なったので、どのようにスタックにしていくかいろいろ悩んだ。appendとextendの違いとか。また、整理したい。
  • NamPy等の外部ライブラリはexe化したときにファイルサイズが重くなってしまいそうなので、とりあえず極力使わない。
  • ファイルサイズは os.path.getsize(フルパスのファイル名)で取得
  • 拡張子は os.path.splitext(ファイル名。フルパスじゃなくても可?)で取得。戻り値はタプルで、一つ目の要素に拡張子を除いたファイル名、二つ目の要素に拡張子が格納される。ピリオドが複数入ってしまっている場合は、後ろの方のピリオドが適用される。(例:test.png.jpg → test.pngと.jpgに分けられる)