다운로드 폴더를 마지막으로 정리한 게 언제인가요?
스크린샷, 견적서, 발표자료, 설치파일, 이름 모를 문서들. 한 폴더에 수백 개씩 쌓여 있는 파일들을 보면서 "언젠가 정리해야지"를 반복하고 계신다면, 오늘이 그날입니다.
이 글에서는 파이썬으로 폴더 안의 파일을 날짜별, 확장자별로 자동 분류하는 스크립트를 만들어봅니다. 한 번 만들어두면 폴더 정리에 시간을 쓰는 일은 다시 없습니다.
사전 준비
필요한 라이브러리
이 글에서 사용하는 라이브러리는 모두 파이썬 기본 내장 라이브러리입니다. 별도 설치가 필요 없습니다.
- 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나 경로 관련 오류가 발생한다면, 아래 관련 포스팅의 환경 설정 가이드를 참고해 보시기 바랍니다.
관련 내용
- [VS Code/Anaconda] Visual Studio Code 프로젝트에 conda 가상환경 적용
- [Anaconda/Windows] Anaconda 다운로드 및 설치
- [Pytorch] ImportError: No module named 'torch' (라이브러리 인식 문제 해결법)
본 포스팅의 코드는 자유롭게 수정 및 배포가 가능하나, 상업적 이용 시에는 출처를 반드시 밝혀주시기 바랍니다.