ファイル操作 中級 更新: 2026-04-26

フォルダ内のファイル一覧を出力するスクリプト

Windowsのフォルダ内にあるファイル一覧を取得し、ファイル名・拡張子・サイズ・更新日時をExcel・CSV・TXT・Markdownのいずれかに書き出すPythonスクリプトです。サブフォルダの再帰走査と拡張子別集計に対応しており、納品物チェックや共有フォルダの棚卸しに使えます。

指定したフォルダ配下のファイルを洗い出し、Excel / CSV / TXT / Markdown のいずれかの形式で一覧表に書き出すスクリプトです。ファイル名・拡張子・サイズ・更新日時を1回で取得でき、サブフォルダの再帰走査にも対応しています。共有フォルダの棚卸しや納品物チェックで、Explorerのプロパティを1つずつ開いて回る手間から解放されます。

このスクリプトでできること

  • フォルダ配下のファイル一覧取得(更新日時・サイズ・拡張子付き)
  • サブフォルダまで再帰的に走査
  • 拡張子別のファイル数・合計サイズを自動集計
  • xlsx / csv / txt / md の4形式で出力
  • アクセス拒否フォルダを自動スキップして処理を継続
フォルダ内のファイル一覧を出力.pybes をダウンロード

.pybes ファイルをPybesにインポートすると、スクリプトと設定フィールドが自動で読み込まれます。

設定フィールド

このスクリプトで使用する設定フィールドです。Pybes上でGUIから値を入力できます。

target_dir フォルダ 必須

対象フォルダ

ファイル一覧を取得する対象フォルダを選択します

output_format ドロップダウン 必須

出力形式

xlsx / csv / txt / md のいずれかを指定します

選択肢: xlsx, csv, txt, md

デフォルト: xlsx

include_detail チェックボックス 必須

詳細情報を含む

拡張子・サイズ・更新日時を含めるかを指定します(true / false)

デフォルト: true

include_subfolders チェックボックス 必須

サブフォルダも含める

指定フォルダ配下のサブフォルダも再帰的に走査します(true / false)

デフォルト: true

output_dir フォルダ 必須

出力先フォルダ

結果ファイルの保存先フォルダを選択します

コード解説

import sys
import json
import os
import datetime

with open(sys.argv[1], encoding="utf-8") as f:
    inputs = json.load(f)

target_dir = inputs["対象フォルダ"]
output_format = inputs["出力形式"]
include_detail = inputs["詳細情報を含む"] == "true"
include_subfolders = inputs["サブフォルダも含める"] == "true"
output_dir = inputs["出力先フォルダ"]

print(f"対象フォルダ: {target_dir}")
print(f"出力形式: {output_format}")
print(f"詳細情報を含む: {include_detail}")
print(f"サブフォルダも含める: {include_subfolders}")

# ファイル一覧を収集
records = []

def collect_files(root):
    try:
        entries = os.scandir(root)
    except PermissionError as e:
        print(f"アクセス拒否(スキップ): {root}", file=sys.stderr)
        return

    for entry in entries:
        try:
            if entry.is_file(follow_symlinks=False):
                rel_path = os.path.relpath(entry.path, target_dir)
                stat = entry.stat()
                size_kb = round(stat.st_size / 1024, 2)
                modified = datetime.datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
                ext = os.path.splitext(entry.name)[1].lower() or "(なし)"
                if include_detail:
                    records.append({
                        "ファイル名": entry.name,
                        "相対パス": rel_path,
                        "拡張子": ext,
                        "サイズ(KB)": size_kb,
                        "更新日時": modified,
                    })
                else:
                    records.append({
                        "ファイル名": entry.name,
                        "相対パス": rel_path,
                        "拡張子": ext,
                        "サイズ(KB)": size_kb,  # 前提: サマリー計算のため詳細OFF時も内部保持
                    })
            elif entry.is_dir(follow_symlinks=False) and include_subfolders:
                collect_files(entry.path)
        except Exception as e:
            print(f"スキップ: {entry.path} - {e}", file=sys.stderr)

collect_files(target_dir)
print(f"{len(records)} 件のファイルを検出しました")

if len(records) == 0:
    print("ファイルが見つかりませんでした。終了します。")
    sys.exit(0)

# サマリー計算
total_count = len(records)
total_size_kb = round(sum(r["サイズ(KB)"] for r in records), 2)
total_size_mb = round(total_size_kb / 1024, 2)

# 拡張子別集計
ext_summary = {}
for r in records:
    ext = r["拡張子"]
    if ext not in ext_summary:
        ext_summary[ext] = {"拡張子": ext, "ファイル数": 0, "合計サイズ(KB)": 0.0}
    ext_summary[ext]["ファイル数"] += 1
    ext_summary[ext]["合計サイズ(KB)"] += r["サイズ(KB)"]
for v in ext_summary.values():
    v["合計サイズ(KB)"] = round(v["合計サイズ(KB)"], 2)
    v["合計サイズ(MB)"] = round(v["合計サイズ(KB)"] / 1024, 2)
ext_summary_rows = sorted(ext_summary.values(), key=lambda x: x["ファイル数"], reverse=True)

# 詳細OFFのとき出力レコードからサイズを除く
if not include_detail:
    output_records = [{"ファイル名": r["ファイル名"], "相対パス": r["相対パス"]} for r in records]
else:
    output_records = records

# タイムスタンプ付きファイル名
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
os.makedirs(output_dir, exist_ok=True)

try:
    if output_format == "xlsx":
        import openpyxl
        from openpyxl.styles import Font, PatternFill, Alignment
        from openpyxl.utils import get_column_letter

        output_path = os.path.join(output_dir, f"filelist_{timestamp}.xlsx")
        wb = openpyxl.Workbook()

        # ── シート1: ファイル一覧 ──
        ws1 = wb.active
        ws1.title = "ファイル一覧"
        headers = list(output_records[0].keys())
        ws1.append(headers)
        for cell in ws1[1]:
            cell.font = Font(bold=True)
            cell.fill = PatternFill("solid", fgColor="D9E1F2")
        for rec in output_records:
            ws1.append([rec[h] for h in headers])
        # サマリー行
        ws1.append([])
        summary_row_idx = ws1.max_row + 1
        ws1.append(["合計", "", f"ファイル数: {total_count} 件 合計サイズ: {total_size_kb} KB({total_size_mb} MB)"])
        for cell in ws1[ws1.max_row]:
            cell.font = Font(bold=True)
            cell.fill = PatternFill("solid", fgColor="FFF2CC")
        # 列幅自動調整
        for col in ws1.columns:
            max_len = max((len(str(cell.value or "")) for cell in col), default=10)
            ws1.column_dimensions[get_column_letter(col[0].column)].width = min(max_len + 4, 60)

        # ── シート2: 拡張子別集計 ──
        ws2 = wb.create_sheet("拡張子別集計")
        ext_headers = ["拡張子", "ファイル数", "合計サイズ(KB)", "合計サイズ(MB)"]
        ws2.append(ext_headers)
        for cell in ws2[1]:
            cell.font = Font(bold=True)
            cell.fill = PatternFill("solid", fgColor="D9E1F2")
        for row in ext_summary_rows:
            ws2.append([row[h] for h in ext_headers])
        # 合計行
        ws2.append([])
        ws2.append(["合計", total_count, total_size_kb, total_size_mb])
        for cell in ws2[ws2.max_row]:
            cell.font = Font(bold=True)
            cell.fill = PatternFill("solid", fgColor="FFF2CC")
        for col in ws2.columns:
            max_len = max((len(str(cell.value or "")) for cell in col), default=10)
            ws2.column_dimensions[get_column_letter(col[0].column)].width = min(max_len + 4, 40)

        wb.save(output_path)

    elif output_format == "csv":
        import csv
        output_path = os.path.join(output_dir, f"filelist_{timestamp}.csv")
        headers = list(output_records[0].keys())
        with open(output_path, "w", encoding="utf-8", newline="") as f:
            writer = csv.DictWriter(f, fieldnames=headers)
            writer.writeheader()
            writer.writerows(output_records)
            # サマリー行
            f.write("\n")
            f.write(f"合計ファイル数,{total_count}\n")
            f.write(f"合計サイズ(KB),{total_size_kb}\n")
            f.write(f"合計サイズ(MB),{total_size_mb}\n")
            f.write("\n拡張子別集計\n")
            ext_writer = csv.DictWriter(f, fieldnames=["拡張子", "ファイル数", "合計サイズ(KB)", "合計サイズ(MB)"])
            ext_writer.writeheader()
            ext_writer.writerows(ext_summary_rows)
            f.write(f"合計,{total_count},{total_size_kb},{total_size_mb}\n")

    elif output_format == "txt":
        output_path = os.path.join(output_dir, f"filelist_{timestamp}.txt")
        headers = list(output_records[0].keys())
        lines = []
        lines.append("=" * 60)
        lines.append("ファイル一覧")
        lines.append("=" * 60)
        lines.append(f"対象フォルダ : {target_dir}")
        lines.append(f"出力日時     : {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        lines.append(f"ファイル数   : {total_count} 件")
        lines.append(f"合計サイズ   : {total_size_kb} KB({total_size_mb} MB)")
        lines.append("=" * 60)
        lines.append("")
        # 列幅を計算して整形
        col_widths = {h: max(len(h), max(len(str(r[h])) for r in output_records)) for h in headers}
        header_line = "  ".join(h.ljust(col_widths[h]) for h in headers)
        lines.append(header_line)
        lines.append("-" * len(header_line))
        for rec in output_records:
            lines.append("  ".join(str(rec[h]).ljust(col_widths[h]) for h in headers))
        lines.append("")
        lines.append("=" * 60)
        lines.append("拡張子別集計")
        lines.append("=" * 60)
        ext_headers = ["拡張子", "ファイル数", "合計サイズ(KB)", "合計サイズ(MB)"]
        ext_col_widths = {h: max(len(h), max(len(str(r[h])) for r in ext_summary_rows)) for h in ext_headers}
        ext_header_line = "  ".join(h.ljust(ext_col_widths[h]) for h in ext_headers)
        lines.append(ext_header_line)
        lines.append("-" * len(ext_header_line))
        for row in ext_summary_rows:
            lines.append("  ".join(str(row[h]).ljust(ext_col_widths[h]) for h in ext_headers))
        lines.append("-" * len(ext_header_line))
        lines.append("  ".join(str(v).ljust(ext_col_widths[h]) for h, v in zip(ext_headers, ["合計", total_count, total_size_kb, total_size_mb])))
        with open(output_path, "w", encoding="utf-8") as f:
            f.write("\n".join(lines))

    elif output_format == "md":
        output_path = os.path.join(output_dir, f"filelist_{timestamp}.md")
        headers = list(output_records[0].keys())
        lines = []
        lines.append("# ファイル一覧")
        lines.append("")
        lines.append(f"対象フォルダ: `{target_dir}`  ")
        lines.append(f"出力日時: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}  ")
        lines.append(f"ファイル数: **{total_count} 件** 合計サイズ: **{total_size_kb} KB({total_size_mb} MB)**  ")
        lines.append("")
        # ファイル一覧テーブル
        lines.append("## ファイル一覧")
        lines.append("")
        lines.append("| " + " | ".join(headers) + " |")
        lines.append("| " + " | ".join(["---"] * len(headers)) + " |")
        for rec in output_records:
            lines.append("| " + " | ".join(str(rec[h]) for h in headers) + " |")
        lines.append("")
        # 拡張子別集計テーブル
        lines.append("## 拡張子別集計")
        lines.append("")
        ext_headers = ["拡張子", "ファイル数", "合計サイズ(KB)", "合計サイズ(MB)"]
        lines.append("| " + " | ".join(ext_headers) + " |")
        lines.append("| " + " | ".join(["---"] * len(ext_headers)) + " |")
        for row in ext_summary_rows:
            lines.append("| " + " | ".join(str(row[h]) for h in ext_headers) + " |")
        lines.append(f"| **合計** | **{total_count}** | **{total_size_kb}** | **{total_size_mb}** |")
        with open(output_path, "w", encoding="utf-8") as f:
            f.write("\n".join(lines))

    else:
        print(f"未対応の出力形式: {output_format}", file=sys.stderr)
        sys.exit(1)

    print(f"出力完了: {output_path}")

except Exception as e:
    print(f"出力エラー: {e}", file=sys.stderr)
    sys.exit(1)

print("完了")
L1–18

先頭で標準ライブラリ(sys / json / os / datetime)を読み込んだ後、Pybesが sys.argv[1] に渡してくるJSON設定ファイルを読み取って inputs 辞書に展開します。チェックボックスはPythonの真偽値ではなく文字列 "true" / "false" として渡ってくるので、比較演算で明示的にboolへ変換している点に注目してください。

L20–56

collect_files 関数は再帰的にフォルダを走査し、各ファイルの「ファイル名・相対パス・拡張子・サイズ・更新日時」を records リストに蓄積します。フォルダ単位の PermissionErroros.scandir 側で捕まえてスキップし、個別エントリで起きた例外は entry 単位で握りつぶしてログに残す設計です。権限エラーのフォルダ1つで処理全体が落ちないようにするための配慮です。

L65–81

総件数・合計サイズ(KB / MB)と、拡張子別の { 拡張子, ファイル数, 合計サイズ } を算出します。集計用辞書を一度構築してから sorted() でファイル数の降順に並べ替える流れで、Python 3.7以降で保証される辞書の挿入順序を前提にしています。

L93–240

output_format の値ごとにxlsx / csv / txt / mdの処理を分岐します。xlsxは openpyxl で「ファイル一覧」「拡張子別集計」の2シート構成、csvは csv.DictWriter、txtは ljust で列幅を揃えた整形テキスト、mdはMarkdownテーブル記法で書き出します。全体を try / except で包んでいるため、出力先の権限不足などもログに残って原因が追えます。

仕組みの詳細

os.scandir で高速にフォルダを走査する

os.scandiros.listdir より高速で、ディレクトリエントリごとに stat(サイズ・更新日時)へ追加のシステムコール無しでアクセスできます。このスクリプトでは collect_files 関数が自分自身を再帰的に呼び出すことで、指定フォルダ配下を丸ごと探索しつつ、各ファイルのメタ情報を一度で取得しています。

拡張子別の集計は辞書で積み上げる

ext_summary 辞書に「拡張子 → ファイル数 / 合計サイズ」を蓄積していき、最後に sorted() でファイル数の多い順に並べ替えます。小数の丸め誤差を避けるため、round() は集計中ではなく最後に1回だけ適用するのがポイントです。

出力形式ごとにロジックを分岐する

xlsxは openpyxl でシート2枚(ファイル一覧 + 拡張子別集計)に整形、csvは csv.DictWriter で書き出し、txtはパディングで擬似的な表を組み、mdはMarkdownテーブル記法で書き出します。どれもファイル名に YYYYMMDD_HHMMSS のタイムスタンプを付けるので、繰り返し実行しても過去の出力を上書きしません。

応用・カスタマイズ

特定の拡張子だけを抽出する

records.append(...) の直前に if ext not in (".xlsx", ".pdf"): continue のようなフィルタを1行足すだけで、対象拡張子を絞り込めます。逆に除外したい拡張子がある場合は if ext in (".tmp", ".log"): continue のように書きます。

更新日時やサイズで並べ替える

collect_files(target_dir) の直後に records.sort(key=lambda r: r["更新日時"], reverse=True) を追加すれば、新しい順に並べ替えられます。サイズ順にしたい場合はキーを r["サイズ(KB)"] に差し替えるだけです。

ファイル名の正規表現フィルタを追加する

先頭に import re を追加し、pattern = re.compile(r"^見積_.*") を用意して if not pattern.match(entry.name): continue を入れれば、ファイル名パターンでの絞り込みができます。「見積_」で始まるファイルだけ、といった要件に素早く対応できます。

よくあるエラーと対処

PermissionError が出てスクリプトが止まります

collect_filesos.scandir に対する PermissionError を捕まえて、該当フォルダを sys.stderr に出力した上でスキップする設計です。それでも全体が止まる場合は、出力先フォルダの書き込み権限が無い可能性が高いです。出力先を C:\Users\<自分>\Documents 配下など書き込み可能な場所に変更してください。

xlsx保存時に PermissionError: [Errno 13] が出ます

同名のxlsxファイルがExcelで開かれていると、wb.save() がファイルロックで失敗します。該当ファイルを閉じるか、出力先フォルダを別の場所に変えて再実行してください。このスクリプトは実行時刻をファイル名に含めるため、同じ秒内で連続実行しない限り衝突は起きにくい設計です。

CSV出力をExcelで開くと日本語が文字化けします

スクリプトはUTF-8でCSVを書き出しますが、ExcelはShift_JISを想定して開くため化けて見えます。Excelの「データ」→「テキストまたはCSVから」でUTF-8を指定してインポートするか、コード側で encoding="utf-8-sig"(BOM付き)に変更するとExcelでも正しく開けます。

FAQ

数万ファイルあるフォルダでも動きますか?

動きます。os.scandiros.listdir よりメモリ効率が良く、5万件程度までは1分以内に処理できる想定です。ただし records リストにメモリで全件保持する設計のため、100万件を超えるような巨大フォルダでは、途中で書き出していくストリーミング処理に改造した方が安全です。

隠しファイルや .DS_Store も含まれますか?

含まれます。os.scandir はOSから見えるすべてのファイルを返すため、Windowsの隠し属性ファイルやmacOS由来の .DS_Store も一覧に出ます。除外したい場合は collect_files 内で if entry.name.startswith("."): continue を追加するのが手軽です。

なぜ pandas ではなく openpyxl を使っているのですか?

セルの色やヘッダー太字など、書式付きで出したいからです。pandas.to_excel でも出力はできますが、書式を細かく制御するには結局 openpyxl のAPIを触る必要があり、最初から直接使った方がコードが短くなります。DataFrame操作が必要な場合のみ pandas を併用してください。

他のよくある質問を見る →
フォルダ内のファイル一覧を出力.pybes をダウンロード

.pybes ファイルをPybesにインポートすると、スクリプトと設定フィールドが自動で読み込まれます。