[Python] 04. 파이썬으로 폴더별 파일 자동 분류하기 (확장자별 정리)
파이썬으로 폴더별 파일 자동 분류하기 (확장자별 정리)
지금 다운로드 폴더 한번 열어보세요. 영수증 PDF, 카톡에서 받은 사진, 회사에서 보낸 엑셀 양식, 누가 던져준 zip 파일까지 다 뒤죽박죽이시죠.
저도 그래서 큰맘 먹고 정리해보겠다고 마우스 잡았다가, 한 30개쯤 옮기고 나서 그냥 닫았어요. 너무 많아서요.
이걸 파이썬한테 시키면 그냥 끝나거든요. 진짜로요.
파일 자동 분류, 왜 파이썬이 답일까
생각해보면 우리가 폴더 정리할 때 하는 일은 단순해요. "이건 이미지니까 사진 폴더, 이건 PDF니까 문서 폴더". 이 기준만 정해주면 나머지는 기계가 훨씬 잘하는 일이거든요.
마치 우체국 분류기 같은 거예요. 주소(=확장자)만 보고 해당 칸으로 던지는 일.
shutil.move랑 rename은 뭐가 달라요?
[Python] 02. 파이썬으로 폴더와 파일 다루기: os, pathlib 완벽 정리
파이썬으로 폴더와 파일 다루기: os, pathlib 완벽 정리지금 잠깐, 다운로드 폴더 한 번 열어보세요.엑셀 파일이 몇 개나 쌓여 있으세요? 저는 방금 세봤더니 47개더라고요. "정리 좀 해야지" 생각만
devsalix.tistory.com
[Python] 03. 파이썬으로 파일 이름 일괄 변경 자동화하기
파이썬으로 파일 이름 일괄 변경 자동화하기다운로드 폴더 열어보면 보고서(1).xlsx, 보고서 (2) 최종.xlsx, 보고서(10).xlsx... 이렇게 쌓여 있고, 정렬은 또 1, 10, 11, 2, 20 순으로 엉망인 상황. 다들 한
devsalix.tistory.com
저번 글(03)에서 rename()으로 파일 이름 바꾸는 거 해봤잖아요. 사실 rename은 같은 폴더 안에서 이동도 되긴 하는데, 큰 함정이 하나 있어요.
다른 드라이브로는 못 옮겨요. C 드라이브에서 D 드라이브로 rename을 시도하면 OSError가 떠버립니다. 저도 처음에 외장하드로 사진 옮기다가 이걸로 한참 헤맸어요.
그래서 이번엔 shutil.move()를 씁니다. 얘는 드라이브가 달라도 알아서 옮겨주거든요.
import shutil # 파일 이동/복사용 표준 라이브러리
from pathlib import Path # 경로 다루기 (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 # 일단 미리보기만 (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으로 미리 확인하는 습관만 들이시면 사고날 일 거의 없어요.
입문편을 마치며
여기까지 따라오셨다면 파일이랑 폴더 다루는 건 이제 거의 다 익히신 거예요. 경로 다루기(02), 이름 일괄 변경(03), 그리고 오늘 한 확장자별 정리까지.
다운로드 폴더 들어갈 때마다 "아 정리해야 하는데" 하던 그 스트레스, 일주일에 한 번 스크립트 한 번 돌리는 걸로 끝낼 수 있어요.
다음 글부터는 진짜 직장인 자동화의 꽃, 엑셀로 들어갑니다. openpyxl이라는 라이브러리로 셀에 값 넣고, 색칠하고, 시트 통째로 만드는 것까지 해볼 거예요. 솔직히 여기서부터가 진짜 재밌어지는 구간이거든요.
제 글이 도움이 되셨다면 댓글 & 공감 부탁드려요 😀