본문 바로가기
카테고리 없음

파이썬으로 폴더 정리 자동화하기 (날짜·확장자별 자동 분류 실전)

by First Adventure 2026. 4. 18.
반응형

다운로드 폴더를 마지막으로 정리한 게 언제인가요?

  스크린샷, 견적서, 발표자료, 설치파일, 이름 모를 문서들. 한 폴더에 수백 개씩 쌓여 있는 파일들을 보면서 "언젠가 정리해야지"를 반복하고 계신다면, 오늘이 그날입니다.

  이 글에서는 파이썬으로 폴더 안의 파일을 날짜별, 확장자별로 자동 분류하는 스크립트를 만들어봅니다. 한 번 만들어두면 폴더 정리에 시간을 쓰는 일은 다시 없습니다.

 

사전 준비

필요한 라이브러리

  이 글에서 사용하는 라이브러리는 모두 파이썬 기본 내장 라이브러리입니다. 별도 설치가 필요 없습니다.

  • os: 파일 경로, 이름, 확장자를 다루는 기본 모듈입니다.
  • shutil: 파일을 실제로 이동(move)하거나 복사(copy)하는 모듈입니다.
  • pathlib: 경로를 객체로 다뤄 코드를 더 직관적으로 만들어주는 모듈입니다.
  • datetime: 파일의 수정일·생성일을 읽어오는 모듈입니다.

 

폴더 구조 예시

  정리 전 다운로드 폴더와, 스크립트 실행 후 자동으로 만들어지는 폴더 구조입니다.

# 정리 전
Downloads/
  screenshot_001.png
  견적서_2024.xlsx
  report_final.pdf
  setup_chrome.exe
  회의록_03.docx
  image_edit.png
  ...

# 정리 후 (확장자별 분류 기준)
Downloads/
  이미지/
    screenshot_001.png
    image_edit.png
  문서/
    견적서_2024.xlsx
    report_final.pdf
    회의록_03.docx
  실행파일/
    setup_chrome.exe

 

Case 1. 확장자별로 분류하기

  가장 기본적인 방식입니다. 파일 확장자를 기준으로 이미지, 문서, 영상, 실행파일 등 카테고리 폴더를 자동으로 만들고 파일을 이동합니다.

# organize_by_ext.py
import os
import shutil
from pathlib import Path

# 분류 기준: 카테고리명 → 해당 확장자 목록
CATEGORY_MAP = {
    "이미지":    [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg", ".heic"],
    "문서":      [".pdf", ".docx", ".doc", ".xlsx", ".xls", ".pptx", ".ppt", ".hwp", ".txt", ".csv"],
    "영상":      [".mp4", ".mov", ".avi", ".mkv", ".wmv"],
    "음악":      [".mp3", ".wav", ".flac", ".aac", ".m4a"],
    "압축파일":  [".zip", ".rar", ".7z", ".tar", ".gz"],
    "실행파일":  [".exe", ".msi", ".dmg", ".pkg"],
    "코드":      [".py", ".js", ".html", ".css", ".java", ".cpp", ".ts"],
}

def get_category(extension: str) -> str:
    """확장자를 받아 해당 카테고리명 반환. 해당 없으면 '기타' 반환."""
    ext = extension.lower()
    for category, extensions in CATEGORY_MAP.items():
        if ext in extensions:
            return category
    return "기타"

def organize_by_extension(folder_path: str, dry_run: bool = True):
    """
    dry_run=True  → 실제 이동 없이 어디로 이동될지만 출력 (미리보기)
    dry_run=False → 실제 파일 이동 실행
    """
    folder = Path(folder_path)
    files = [f for f in folder.iterdir() if f.is_file()]

    print(f"[INFO] 대상 폴더: {folder}")
    print(f"[INFO] 발견된 파일 수: {len(files)}개")
    print(f"[INFO] 모드: {'미리보기 (실제 이동 안 함)' if dry_run else '실행'}\n")

    moved = 0
    for file in files:
        category = get_category(file.suffix)
        dest_dir = folder / category
        dest_path = dest_dir / file.name

        print(f"  {file.name}  →  {category}/")

        if not dry_run:
            dest_dir.mkdir(exist_ok=True)
            # 같은 이름의 파일이 이미 있으면 덮어쓰지 않고 번호 붙이기
            if dest_path.exists():
                stem = file.stem
                suffix = file.suffix
                count = 1
                while dest_path.exists():
                    dest_path = dest_dir / f"{stem}_{count}{suffix}"
                    count += 1
            shutil.move(str(file), str(dest_path))
            moved += 1

    if not dry_run:
        print(f"\n[완료] {moved}개 파일 이동 완료")
    else:
        print(f"\n실제로 이동하려면 dry_run=False 로 변경 후 실행하세요.")

# 미리보기 먼저 확인
organize_by_extension("C:/Users/사용자이름/Downloads", dry_run=True)

# 확인 후 실제 실행
# organize_by_extension("C:/Users/사용자이름/Downloads", dry_run=False)

 

Case 2. 날짜별로 분류하기

  파일의 수정일을 기준으로 2024-01, 2024-02 형태의 연월 폴더를 자동으로 만들고 분류합니다. 사진이나 스크린샷처럼 날짜 기준 정리가 자연스러운 파일에 적합합니다.

# organize_by_date.py
import os
import shutil
from pathlib import Path
from datetime import datetime

def organize_by_date(folder_path: str, dry_run: bool = True):
    folder = Path(folder_path)
    files = [f for f in folder.iterdir() if f.is_file()]

    print(f"[INFO] 대상 폴더: {folder}")
    print(f"[INFO] 발견된 파일 수: {len(files)}개\n")

    moved = 0
    for file in files:
        # 파일의 마지막 수정일 가져오기
        mtime = os.path.getmtime(file)
        date_str = datetime.fromtimestamp(mtime).strftime("%Y-%m")  # 예: 2024-03

        dest_dir = folder / date_str
        dest_path = dest_dir / file.name

        print(f"  {file.name}  →  {date_str}/")

        if not dry_run:
            dest_dir.mkdir(exist_ok=True)
            if dest_path.exists():
                stem, suffix = file.stem, file.suffix
                count = 1
                while dest_path.exists():
                    dest_path = dest_dir / f"{stem}_{count}{suffix}"
                    count += 1
            shutil.move(str(file), str(dest_path))
            moved += 1

    if not dry_run:
        print(f"\n[완료] {moved}개 파일 이동 완료")

organize_by_date("C:/Users/사용자이름/Downloads", dry_run=True)
# organize_by_date("C:/Users/사용자이름/Downloads", dry_run=False)

 

Case 3. 날짜 + 확장자 두 단계로 분류하기

  연월 폴더 안에 확장자 카테고리 폴더를 한 단계 더 만드는 방식입니다. 파일이 매우 많을 때 가장 체계적으로 관리할 수 있습니다.

# organize_by_date_and_ext.py
import os
import shutil
from pathlib import Path
from datetime import datetime

CATEGORY_MAP = {
    "이미지":    [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg", ".heic"],
    "문서":      [".pdf", ".docx", ".doc", ".xlsx", ".xls", ".pptx", ".ppt", ".hwp", ".txt", ".csv"],
    "영상":      [".mp4", ".mov", ".avi", ".mkv", ".wmv"],
    "음악":      [".mp3", ".wav", ".flac", ".aac", ".m4a"],
    "압축파일":  [".zip", ".rar", ".7z", ".tar", ".gz"],
    "실행파일":  [".exe", ".msi", ".dmg", ".pkg"],
    "코드":      [".py", ".js", ".html", ".css", ".java", ".cpp", ".ts"],
}

def get_category(extension: str) -> str:
    ext = extension.lower()
    for category, extensions in CATEGORY_MAP.items():
        if ext in extensions:
            return category
    return "기타"

def organize_by_date_and_ext(folder_path: str, dry_run: bool = True):
    folder = Path(folder_path)
    files = [f for f in folder.iterdir() if f.is_file()]

    print(f"[INFO] 대상 폴더: {folder}")
    print(f"[INFO] 발견된 파일 수: {len(files)}개\n")

    moved = 0
    for file in files:
        mtime = os.path.getmtime(file)
        date_str = datetime.fromtimestamp(mtime).strftime("%Y-%m")
        category = get_category(file.suffix)

        # 결과 구조: 폴더 / 2024-03 / 이미지 / 파일명
        dest_dir = folder / date_str / category
        dest_path = dest_dir / file.name

        print(f"  {file.name}  →  {date_str}/{category}/")

        if not dry_run:
            dest_dir.mkdir(parents=True, exist_ok=True)
            if dest_path.exists():
                stem, suffix = file.stem, file.suffix
                count = 1
                while dest_path.exists():
                    dest_path = dest_dir / f"{stem}_{count}{suffix}"
                    count += 1
            shutil.move(str(file), str(dest_path))
            moved += 1

    if not dry_run:
        print(f"\n[완료] {moved}개 파일 이동 완료")

organize_by_date_and_ext("C:/Users/사용자이름/Downloads", dry_run=True)
# organize_by_date_and_ext("C:/Users/사용자이름/Downloads", dry_run=False)

 

실전에서 바로 쓰는 완성 코드

  위 내용을 모두 담아 실행 옵션을 선택할 수 있는 완성본입니다. --mode 옵션으로 분류 방식을 선택하고, --dry-run 옵션으로 실제 이동 전 미리보기를 확인할 수 있습니다.

실행 예시

# 확장자별 분류 미리보기
python organize.py --folder "C:/Users/사용자이름/Downloads" --mode ext --dry-run

# 날짜별 분류 실제 실행
python organize.py --folder "C:/Users/사용자이름/Downloads" --mode date

# 날짜+확장자 두 단계 분류 실제 실행
python organize.py --folder "C:/Users/사용자이름/Downloads" --mode both
# organize.py
import os
import shutil
import argparse
from pathlib import Path
from datetime import datetime

CATEGORY_MAP = {
    "이미지":    [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg", ".heic"],
    "문서":      [".pdf", ".docx", ".doc", ".xlsx", ".xls", ".pptx", ".ppt", ".hwp", ".txt", ".csv"],
    "영상":      [".mp4", ".mov", ".avi", ".mkv", ".wmv"],
    "음악":      [".mp3", ".wav", ".flac", ".aac", ".m4a"],
    "압축파일":  [".zip", ".rar", ".7z", ".tar", ".gz"],
    "실행파일":  [".exe", ".msi", ".dmg", ".pkg"],
    "코드":      [".py", ".js", ".html", ".css", ".java", ".cpp", ".ts"],
}

def get_category(extension: str) -> str:
    ext = extension.lower()
    for category, extensions in CATEGORY_MAP.items():
        if ext in extensions:
            return category
    return "기타"

def get_date_str(file: Path) -> str:
    mtime = os.path.getmtime(file)
    return datetime.fromtimestamp(mtime).strftime("%Y-%m")

def resolve_dest(dest_dir: Path, file: Path) -> Path:
    """동일 파일명이 있으면 _1, _2 번호를 붙여 충돌 방지"""
    dest_path = dest_dir / file.name
    if not dest_path.exists():
        return dest_path
    count = 1
    while dest_path.exists():
        dest_path = dest_dir / f"{file.stem}_{count}{file.suffix}"
        count += 1
    return dest_path

def organize(folder_path: str, mode: str, dry_run: bool):
    folder = Path(folder_path)
    if not folder.exists():
        print(f"[오류] 폴더를 찾을 수 없습니다: {folder_path}")
        return

    files = [f for f in folder.iterdir() if f.is_file()]
    print(f"[INFO] 대상 폴더   : {folder}")
    print(f"[INFO] 분류 방식   : {mode}")
    print(f"[INFO] 발견 파일 수: {len(files)}개")
    print(f"[INFO] 모드        : {'미리보기' if dry_run else '실행'}\n")

    moved = 0
    for file in files:
        if mode == "ext":
            dest_dir = folder / get_category(file.suffix)
        elif mode == "date":
            dest_dir = folder / get_date_str(file)
        else:  # both
            dest_dir = folder / get_date_str(file) / get_category(file.suffix)

        dest_path = resolve_dest(dest_dir, file)
        print(f"  {file.name}  →  {dest_path.relative_to(folder)}")

        if not dry_run:
            dest_dir.mkdir(parents=True, exist_ok=True)
            shutil.move(str(file), str(dest_path))
            moved += 1

    if not dry_run:
        print(f"\n[완료] {moved}개 파일 이동 완료")
    else:
        print(f"\n실제로 이동하려면 --dry-run 옵션을 제거하고 실행하세요.")

def get_args():
    p = argparse.ArgumentParser(description="폴더 자동 정리 스크립트")
    p.add_argument("--folder",   required=True,  help="정리할 폴더 경로")
    p.add_argument("--mode",     default="ext",  choices=["ext", "date", "both"],
                   help="분류 방식: ext(확장자별) / date(날짜별) / both(날짜+확장자)")
    p.add_argument("--dry-run",  action="store_true", help="미리보기 (실제 이동 안 함)")
    return p.parse_args()

if __name__ == "__main__":
    args = get_args()
    organize(args.folder, args.mode, args.dry_run)

 

코드 핵심 옵션 설명

  • --folder: 정리할 폴더의 경로를 지정합니다. Windows는 큰따옴표로 감싸야 공백이 포함된 경로도 정상 인식됩니다.
  • --mode ext: 확장자 기준으로만 분류합니다. 이미지, 문서, 영상 등 카테고리 폴더가 생성됩니다.
  • --mode date: 파일의 수정일 기준 연월 폴더로 분류합니다. 사진·스크린샷 정리에 적합합니다.
  • --mode both: 날짜 폴더 안에 확장자 카테고리 폴더를 한 단계 더 만듭니다. 파일이 매우 많을 때 가장 체계적입니다.
  • --dry-run: 실제 파일을 이동하지 않고 어디로 이동될지만 출력합니다. 처음 실행할 때 반드시 먼저 확인하세요.
  • 파일명 충돌 방지: 같은 이름의 파일이 이미 있으면 파일명_1.확장자 형태로 번호를 붙여 기존 파일을 덮어쓰지 않습니다.

 

자주 발생하는 문제와 해결법

문제 1. 하위 폴더 안의 파일까지 함께 정리하고 싶을 때

  기본 코드는 지정한 폴더의 파일만 처리합니다. 하위 폴더까지 재귀적으로 탐색하려면 iterdir() 대신 rglob("*")를 사용합니다.

# 하위 폴더까지 모든 파일 탐색
files = [f for f in folder.rglob("*") if f.is_file()]

 

문제 2. 특정 확장자는 이동하지 않고 제외하고 싶을 때

  파일 목록을 가져올 때 제외할 확장자를 필터링합니다.

EXCLUDE_EXT = {".tmp", ".ini", ".log"}  # 제외할 확장자

files = [
    f for f in folder.iterdir()
    if f.is_file() and f.suffix.lower() not in EXCLUDE_EXT
]

 

문제 3. PermissionError가 발생할 때

  다른 프로그램이 파일을 열고 있거나, 시스템 파일에 접근하려 할 때 발생합니다. try-except로 해당 파일만 건너뛰고 나머지는 계속 처리합니다.

try:
    shutil.move(str(file), str(dest_path))
except PermissionError:
    print(f"  [건너뜀] 파일이 사용 중입니다: {file.name}")
except Exception as e:
    print(f"  [오류] {file.name} → {e}")

 

마치며..

  오늘은 파이썬으로 폴더 안의 파일을 확장자별, 날짜별로 자동 분류하는 방법을 살펴보았습니다. 정리된 환경은 생각보다 훨씬 많은 시간을 돌려줍니다. 필요한 파일을 찾는 데 쓰던 시간, 중복 파일을 확인하는 시간, 폴더를 열었다 닫았다 하는 시간. 이 모든 것을 파이썬이 대신합니다.

  코드 실행 중 PermissionError나 경로 관련 오류가 발생한다면, 아래 관련 포스팅의 환경 설정 가이드를 참고해 보시기 바랍니다.

 

관련 내용

 


본 포스팅의 코드는 자유롭게 수정 및 배포가 가능하나, 상업적 이용 시에는 출처를 반드시 밝혀주시기 바랍니다.

 

반응형