파이썬으로 폴더별 파일 자동 분류하기 (확장자별 정리)
지금 다운로드 폴더 한번 열어보세요. 영수증 PDF, 카톡에서 받은 사진, 회사에서 보낸 엑셀 양식, 누가 던져준 zip 파일까지 다 뒤죽박죽이시죠.
저도 그래서 큰맘 먹고 정리해보겠다고 마우스 잡았다가, 한 30개쯤 옮기고 나서 그냥 닫았어요. 너무 많아서요.
이걸 파이썬한테 시키면 그냥 끝나거든요. 진짜로요.
파일 자동 분류, 왜 파이썬이 답일까
생각해보면 우리가 폴더 정리할 때 하는 일은 단순해요. "이건 이미지니까 사진 폴더, 이건 PDF니까 문서 폴더". 이 기준만 정해주면 나머지는 기계가 훨씬 잘하는 일이거든요.
마치 우체국 분류기 같은 거예요. 주소(=확장자)만 보고 해당 칸으로 던지는 일.
shutil.move랑 rename은 뭐가 달라요?
저번 글(auto-03)에서 rename()으로 파일 이름 바꾸는 거 해봤잖아요. 사실 rename은 같은 폴더 안에서 이동도 되긴 하는데, 큰 함정이 하나 있어요.
다른 드라이브로는 못 옮겨요. C 드라이브에서 D 드라이브로 rename을 시도하면 OSError가 떠버립니다. 저도 처음에 외장하드로 사진 옮기다가 이걸로 한참 헤맸어요.
그래서 이번엔 shutil.move()를 씁니다. 얘는 드라이브가 달라도 알아서 옮겨주거든요.
import shutil # 파일 이동/복사용 표준 라이브러리
from pathlib import Path # 경로 다루기 (auto-02에서 배운 그거)
src = Path("C:/Users/me/Downloads/사진.jpg") # 원본
dst = Path("D:/정리함/이미지/사진.jpg") # 대상
shutil.move(str(src), str(dst)) # 끝
str()로 감싼 이유는 옛날 파이썬 버전에서도 안전하게 동작하게 하려는 거예요. 습관처럼 붙여주시면 됩니다.
확장자별 정리의 핵심: 매핑 딕셔너리
이번 글의 진짜 주인공은 이 딕셔너리 하나예요.
CATEGORIES = {
"이미지": [".jpg", ".jpeg", ".png", ".gif", ".webp"], # 사진류
"문서": [".pdf", ".docx", ".hwp", ".txt"], # 문서류
"엑셀": [".xlsx", ".xls", ".csv"], # 표 데이터
"압축": [".zip", ".rar", ".7z"], # 압축파일
"영상": [".mp4", ".mov", ".avi", ".mkv"], # 동영상
}
본인 취향대로 마음껏 바꾸셔도 돼요. "한글 파일은 따로 빼고 싶다" 하시면 새 카테고리 추가하시면 그만이거든요.
이제 확장자를 받으면 어떤 카테고리인지 알려주는 함수를 만들어 볼게요.
def find_category(suffix):
suffix = suffix.lower() # .JPG 같은 대문자 방어
for cat, exts in CATEGORIES.items(): # 카테고리들을 하나씩 훑으면서
if suffix in exts: # 해당 확장자 리스트에 있으면
return cat # 그 카테고리 이름 반환
return "기타" # 어디에도 없으면 기타
suffix.lower() 이거 진짜 중요해요. .JPG랑 .jpg는 파이썬 입장에서 완전 다른 문자열이거든요. 이거 안 해주면 핸드폰에서 받은 .JPG 파일들이 죄다 "기타"로 빠지는 슬픈 일이 생깁니다.
폴더가 없으면 만들어주기
분류해서 옮기려고 했더니 "이미지" 폴더 자체가 아직 없을 수 있잖아요. 그 상태로 move 하면 FileNotFoundError 만나요.
그래서 옮기기 전에 폴더부터 만들어 둡니다.
target_folder = Path("C:/Users/me/Downloads/이미지")
target_folder.mkdir(parents=True, exist_ok=True)
옵션 두 개만 기억하시면 돼요.
parents=True: 중간 폴더가 없어도 다 만들어줘요exist_ok=True: 이미 있어도 에러 안 내고 그냥 통과해요
이 두 개 안 주면 두 번째 돌릴 때부터 에러 폭탄이에요. 꼭 같이 써주세요.
같은 이름 파일이 있으면?
report.pdf를 문서 폴더로 옮기려는데 거기 똑같이 report.pdf가 있다면? 그냥 덮어써집니다. shutil.move는 윈도우에서도 인정사정 없이 덮어쓰거든요. 모르고 돌리면 소중한 파일이 한 방에 사라져요.
그래서 이름 뒤에 _1, _2 붙여주는 함수를 하나 만들어 둡니다.
def resolve_conflict(target):
if not target.exists(): # 같은 이름이 없으면
return target # 그대로 사용
stem, suffix, parent = target.stem, target.suffix, target.parent
i = 1
while (parent / f"{stem}_{i}{suffix}").exists(): # 빈 번호 찾을 때까지
i += 1
return parent / f"{stem}_{i}{suffix}" # report_1.pdf, report_2.pdf...
while True처럼 보여서 처음엔 무서우실 수 있는데, 빈 자리만 찾으면 바로 return으로 빠져나오니까 안전해요.
파이썬 파일 정리 스크립트 전체 코드
자, 이제 다 모아볼게요. 이게 메인입니다.
from pathlib import Path
import shutil
DOWNLOADS = Path.home() / "Downloads" # 본인 다운로드 폴더 자동 인식
DRY_RUN = True # 일단 미리보기만 (auto-03에서 배운 그 패턴)
CATEGORIES = {
"이미지": [".jpg", ".jpeg", ".png", ".gif", ".webp"],
"문서": [".pdf", ".docx", ".hwp", ".txt"],
"엑셀": [".xlsx", ".xls", ".csv"],
"압축": [".zip", ".rar", ".7z"],
"영상": [".mp4", ".mov", ".avi", ".mkv"],
}
def find_category(suffix):
suffix = suffix.lower()
for cat, exts in CATEGORIES.items():
if suffix in exts:
return cat
return "기타"
def resolve_conflict(target):
if not target.exists():
return target
stem, suffix, parent = target.stem, target.suffix, target.parent
i = 1
while (parent / f"{stem}_{i}{suffix}").exists():
i += 1
return parent / f"{stem}_{i}{suffix}"
count = 0
for file in DOWNLOADS.iterdir(): # 다운로드 폴더 안의 모든 항목
if file.is_dir(): # 폴더면 건드리지 않기 (중요!)
continue
category = find_category(file.suffix)
target_folder = DOWNLOADS / category
target_folder.mkdir(exist_ok=True)
target = resolve_conflict(target_folder / file.name)
if DRY_RUN:
print(f"[미리보기] {file.name} → {category}/{target.name}")
else:
shutil.move(str(file), str(target))
print(f"[이동] {file.name} → {category}/{target.name}")
count += 1
print(f"\n총 {count}개 처리됨 (DRY_RUN={DRY_RUN})")
실행 결과는 대충 이렇게 나와요.
[미리보기] 회의록.pdf → 문서/회의록.pdf
[미리보기] 강아지.jpg → 이미지/강아지.jpg
[미리보기] 매출표.xlsx → 엑셀/매출표.xlsx
[미리보기] 강아지.jpg → 이미지/강아지_1.jpg
...
총 47개 처리됨 (DRY_RUN=True)
여기서 꼭 짚고 싶은 부분이 if file.is_dir(): continue예요. 이미 만들어진 "이미지" 폴더를 또 분류하려고 하면 폴더 안의 폴더 안의 폴더… 카오스가 펼쳐집니다. 폴더는 무조건 무시하세요.
미리보기 결과 보시고 마음에 드시면 DRY_RUN = False로 바꿔서 진짜 실행하시면 돼요.
보너스: 사진을 촬영월별로 정리
확장자별 분류만으로는 부족하실 수도 있어요. 사진이 1000장 있는데 다 "이미지" 폴더에 들어가면 그것도 카오스잖아요.
수정 시간 기준으로 월별 폴더에 넣어주는 짧은 스크립트입니다.
from pathlib import Path
from datetime import datetime
import shutil
PHOTOS = Path(r"C:\사진정리\원본") # r" " 붙이면 역슬래시 신경 안 써도 돼요
for file in PHOTOS.iterdir():
if not file.is_file():
continue
mtime = datetime.fromtimestamp(file.stat().st_mtime) # 수정 시간
folder = PHOTOS / mtime.strftime("%Y-%m") # "2025-03" 같은 폴더명
folder.mkdir(exist_ok=True)
shutil.move(str(file), str(folder / file.name))
stat().st_mtime이 파일 수정 시간이에요. 카메라가 기록한 실제 촬영 시간(EXIF)이랑은 살짝 다를 수 있는데, 그건 외부 라이브러리가 필요해서 다음 기회에 다룰게요.
자주 만나는 에러들
| 에러 메시지 | 원인 | 해결 |
|---|---|---|
FileNotFoundError |
대상 폴더가 없음 | mkdir(exist_ok=True) 먼저 |
PermissionError |
파일이 다른 프로그램에서 열려 있음 | 엑셀이나 한글 같은 거 닫고 다시 |
OSError [WinError 17] |
rename으로 다른 드라이브 이동 | shutil.move 쓰기 |
NameError: shutil |
import 빼먹음 | import shutil 추가 |
특히 두 번째, 엑셀 파일 열어둔 채로 돌리면 백퍼 에러나요. 저도 한참 헤맸던 부분이에요.
실습 과제
- 본인 다운로드 폴더 경로로 위 메인 스크립트 돌려보세요. 꼭
DRY_RUN = True상태로요. - 출력 결과 쭉 보시고, 분류가 마음에 들면
False로 바꿔서 진짜 실행. - 본인이 자주 받는 확장자(예:
.psd,.ai) 있으면 CATEGORIES에 추가해서 한번 더 돌려보세요.
처음엔 무서우실 수 있는데, dry-run으로 미리 확인하는 습관만 들이시면 사고날 일 거의 없어요.
입문편을 마치며
여기까지 따라오셨다면 파일이랑 폴더 다루는 건 이제 거의 다 익히신 거예요. 경로 다루기(auto-02), 이름 일괄 변경(auto-03), 그리고 오늘 한 확장자별 정리까지.
다운로드 폴더 들어갈 때마다 "아 정리해야 하는데" 하던 그 스트레스, 일주일에 한 번 스크립트 한 번 돌리는 걸로 끝낼 수 있어요.
다음 글부터는 진짜 직장인 자동화의 꽃, 엑셀로 들어갑니다. openpyxl이라는 라이브러리로 셀에 값 넣고, 색칠하고, 시트 통째로 만드는 것까지 해볼 거예요. 솔직히 여기서부터가 진짜 재밌어지는 구간이거든요.
제 글이 도움이 되셨다면 댓글 & 공감 부탁드려요 😀
'Application > Python' 카테고리의 다른 글
| [Python] 06. 파이썬으로 여러 엑셀 파일 하나로 합치기 (0) | 2026.06.16 |
|---|---|
| [Python] 05. 파이썬 엑셀 자동화, openpyxl로 시작하기 (0) | 2026.06.15 |
| [Python] 03. 파이썬으로 파일 이름 일괄 변경 자동화하기 (0) | 2026.06.11 |
| [Python] 02. 파이썬으로 폴더와 파일 다루기: os, pathlib 완벽 정리 (0) | 2026.06.10 |
| [Python] 01. 파이썬 자동화 첫 시작: 야근 줄이는 코드 한 줄의 힘 (0) | 2026.06.09 |