매일 반복하는 파일 정리 작업 자동화하기 (Python 스크립트)

다운로드 폴더를 열 때마다 뒤죽박죽 쌓인 파일들 때문에 한숨 쉬어본 적 있으신가요? 이미지, 문서, 압축파일, 설치파일이 한데 뒤섞여 있으면 필요한 파일을 찾는 데만 몇 분씩 허비하게 됩니다. 매일 반복하는 이 지루한 작업, 파이썬 스크립트 하나면 완전히 자동화할 수 있습니다. 오늘은 파일 자동 정리를 위한 폴더 정리 스크립트를 직접 만들어보겠습니다.

왜 파일 정리를 자동화해야 할까

수작업으로 파일을 옮기다 보면 실수로 중요한 파일을 삭제하거나 엉뚱한 폴더에 넣는 일이 생깁니다. 게다가 매번 같은 작업을 반복하는 것 자체가 시간 낭비죠. 자동화 스크립트를 한 번 만들어두면 확장자별로, 혹은 생성 날짜별로 순식간에 폴더가 정리되고, 실수의 여지도 크게 줄어듭니다.

기본 아이디어

핵심 로직은 간단합니다. 지정한 폴더 안의 파일들을 하나씩 확인하면서 확장자를 기준으로 분류한 뒤, 미리 만들어둔 하위 폴더로 이동시키는 방식입니다. 파이썬의 os와 shutil 모듈만 있으면 충분합니다.

import os
import shutil
from pathlib import Path

# 정리할 폴더 경로
TARGET_DIR = Path.home() / "Downloads"

# 확장자별 분류 규칙
FILE_TYPES = {
    "이미지": [".jpg", ".jpeg", ".png", ".gif", ".webp"],
    "문서": [".pdf", ".docx", ".txt", ".xlsx", ".pptx"],
    "압축파일": [".zip", ".rar", ".7z"],
    "설치파일": [".exe", ".msi"],
}

def organize_files(target_dir: Path):
    for file in target_dir.iterdir():
        if file.is_file():
            moved = False
            for folder_name, extensions in FILE_TYPES.items():
                if file.suffix.lower() in extensions:
                    dest_folder = target_dir / folder_name
                    dest_folder.mkdir(exist_ok=True)
                    shutil.move(str(file), str(dest_folder / file.name))
                    moved = True
                    break
            if not moved:
                dest_folder = target_dir / "기타"
                dest_folder.mkdir(exist_ok=True)
                shutil.move(str(file), str(dest_folder / file.name))

if __name__ == "__main__":
    organize_files(TARGET_DIR)
    print("파일 정리가 완료되었습니다.")
확장자별 파일 정리 스크립트 실제 실행 결과
실제로 돌린 결과입니다. 7개 파일이 이미지·문서·압축파일·설치파일 폴더로 나뉘었습니다.

이 스크립트는 다운로드 폴더의 파일을 확장자에 따라 이미지, 문서, 압축파일, 설치파일, 기타 폴더로 자동 분류합니다. 원하는 확장자 규칙은 FILE_TYPES 딕셔너리만 수정하면 얼마든지 확장할 수 있습니다.

매일 자동 실행되게 만들기

스크립트를 매번 손으로 실행하는 것도 번거롭다면, 윈도우 작업 스케줄러(Task Scheduler)에 등록해서 매일 정해진 시간에 자동 실행되도록 설정할 수 있습니다. python organize.py를 실행하는 배치 파일(.bat)을 만들고, 이를 작업 스케줄러의 트리거로 등록하면 컴퓨터를 켤 때마다 혹은 특정 시각마다 자동으로 폴더가 정리됩니다.

조금 더 똑똑하게 만들기

기본 버전에 익숙해졌다면 다음 기능들을 추가해보는 것도 좋습니다.

  • 날짜별 하위 폴더 생성: 파일 생성일을 기준으로 2026-07처럼 월별 폴더를 만들어 세분화
  • 중복 파일 처리: 같은 이름의 파일이 이미 있을 경우 자동으로 번호를 붙여 저장
  • 로그 기록: 어떤 파일이 언제 어디로 이동했는지 텍스트 파일로 기록해두면 나중에 추적이 쉬움
  • 여러 폴더 동시 정리: 다운로드뿐 아니라 바탕화면, 문서 폴더까지 한 번에 정리

직접 돌려 보니 — 1,000개는 0.7초, 그런데 함정이 세 개 있었습니다

위 스크립트를 실제 다운로드 폴더 대신 시험용 폴더에서 돌려 봤습니다. 환경은 Windows 11, Python 3.12.10, Ryzen 9 6900HX 노트북에 NVMe SSD입니다. 확장자 12종(.jpg, .JPG, .PDF, .csv, .tar.gz, 확장자 없음 등)을 섞은 1~50KB짜리 더미 파일 1,000개를 만들고 정리를 시켰습니다.

속도는 문제가 아니었습니다. 1,000개를 다섯 폴더로 나누는 데 0.69초가 걸렸습니다. 대문자 확장자도 suffix.lower() 덕분에 제대로 분류됐습니다. 다만 결과를 보면 기타 폴더에 253개가 들어갔습니다. .csv는 규칙에 없었고, report.tar.gz는 파이썬이 확장자를 .gz로만 읽기 때문에 압축파일로 분류되지 않았습니다. 기타 폴더가 이렇게 커지면 정리한 의미가 줄어드니, 자주 받는 확장자는 규칙에 미리 넣어 두는 게 좋습니다.

문제는 속도가 아니라 예외 상황에서 나왔습니다. 일부러 세 가지 상황을 만들어 원래 스크립트와 고친 스크립트를 똑같이 돌렸습니다.

시험 상황원래 스크립트고친 스크립트
더미 파일 1,000개 정리0.692초, 기타 253개0.677초, 기타 85개(.csv·.gz 규칙 추가)
이미지/report.png가 이미 있는데 새 report.png가 들어옴기존 파일이 경고 없이 덮어써짐report (1).png로 저장, 기존 파일 그대로
정리 도중 b.txt를 다른 프로그램이 열고 있음PermissionError로 전체 중단. b.txt는 문서 폴더에 복사본이 생기고 원본도 남음, 뒤 파일은 처리 안 됨b.txt만 건너뛰고 기록, 나머지는 정리
같은 폴더에서 두 번째 실행해당 없음처음 고친 판은 자기 로그 파일을 기타로 옮김 → 로그 파일 제외 후 0개

함정 1: 같은 이름 파일은 조용히 덮어써집니다

가장 위험한 건 덮어쓰기였습니다. 이미지 폴더에 있던 report.png(내용 OLD-IMPORTANT)가 새로 들어온 같은 이름 파일로 바뀌었고, 오류도 경고도 없었습니다. shutil.move는 이름 바꾸기가 실패하면 복사한 뒤 원본을 지우는 방식으로 넘어가는데, 이때 대상 파일이 있으면 그대로 덮어씁니다. 다운로드 폴더에는 image.png, document.pdf처럼 흔한 이름이 반복해서 들어오기 때문에 매일 돌리면 실제로 일어날 수 있는 일입니다. 고친 판은 대상이 이미 있으면 이름 (1).확장자, 이름 (2).확장자 순으로 빈 이름을 찾습니다.

함정 2: 사용 중인 파일 하나에 전체가 멈추고 복사본이 남습니다

메모장이나 엑셀이 파일을 열고 있으면 윈도우는 그 파일의 이동·삭제를 막습니다. 원래 스크립트는 여기서 PermissionError: [WinError 32]로 멈췄는데, 결과가 더 지저분했습니다. 문서 폴더에 b.txt 복사본이 생겼고 원본도 그 자리에 남았습니다. 복사는 됐는데 원본 삭제에서 막힌 것입니다. 그 뒤에 있던 z.txt는 아예 처리되지 않았습니다. 고친 판은 shutil.move 대신 Path.rename을 씁니다. 같은 드라이브 안 이동이라 복사가 필요 없고, 실패하면 아무것도 바꾸지 않은 채 예외가 납니다. 그 파일만 건너뛰고 기록에 남긴 뒤 다음 파일로 넘어갑니다.

함정 3: 로그 파일이 자기 자신을 정리해 버립니다

이동 기록을 남기려고 대상 폴더에 정리기록.log를 쓰게 했더니, 두 번째 실행에서 “1개 이동”이 찍혔습니다. 확인해 보니 로그 파일 자체가 기타 폴더로 옮겨져 있었습니다. 정리 대상 폴더 안에 무언가를 만들면 다음 실행 때 그것도 정리 대상이 됩니다. 로그 파일 이름을 제외 목록에 넣은 뒤에는 두 번째 실행 결과가 0개로 나왔습니다. 내려받는 중인 .crdownload·.part 파일도 같은 이유로 건너뛰게 했습니다.

고친 스크립트

위 세 가지를 반영한 전체 코드입니다. 표준 라이브러리만 씁니다.

import sys
from datetime import datetime
from pathlib import Path

FILE_TYPES = {
    "이미지": [".jpg", ".jpeg", ".png", ".gif", ".webp"],
    "문서": [".pdf", ".docx", ".txt", ".xlsx", ".pptx", ".csv"],
    "압축파일": [".zip", ".rar", ".7z", ".gz"],
    "설치파일": [".exe", ".msi"],
}
SKIP_SUFFIXES = {".crdownload", ".part", ".tmp"}  # 내려받는 중인 파일
LOG_NAME = "정리기록.log"

def unique_path(dest: Path) -> Path:
    """같은 이름이 있으면 '이름 (1).확장자'처럼 번호를 붙인다."""
    if not dest.exists():
        return dest
    n = 1
    while True:
        candidate = dest.with_name(f"{dest.stem} ({n}){dest.suffix}")
        if not candidate.exists():
            return candidate
        n += 1

def folder_for(file: Path) -> str:
    for folder_name, extensions in FILE_TYPES.items():
        if file.suffix.lower() in extensions:
            return folder_name
    return "기타"

def organize_files(target_dir: Path, dry_run=False):
    moved, skipped = 0, []
    log = []
    for file in list(target_dir.iterdir()):
        if not file.is_file() or file.name == LOG_NAME or file.suffix.lower() in SKIP_SUFFIXES:
            continue
        dest = unique_path(target_dir / folder_for(file) / file.name)
        if dry_run:
            log.append(f"(미리보기) {file.name} -> {dest.parent.name}/{dest.name}")
            continue
        try:
            dest.parent.mkdir(exist_ok=True)
            file.rename(dest)  # 같은 드라이브 안 이동이라 복사 없이 이름만 바뀐다
            moved += 1
            log.append(f"{file.name} -> {dest.parent.name}/{dest.name}")
        except PermissionError:
            skipped.append(file.name)
            log.append(f"건너뜀(사용 중) {file.name}")
    stamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with open(target_dir / LOG_NAME, "a", encoding="utf-8") as f:
        for line in log:
            f.write(f"{stamp}  {line}\n")
    return moved, skipped

if __name__ == "__main__":
    target = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.home() / "Downloads"
    dry = "--dry-run" in sys.argv
    moved, skipped = organize_files(target, dry_run=dry)
    print(f"이동 {moved}개, 건너뜀 {len(skipped)}개 {skipped}")

처음에는 꼭 --dry-run으로 돌려 보세요. 파일은 하나도 옮기지 않고, 어디로 갈 예정인지만 정리기록.log에 적습니다. 파일 세 개를 넣은 시험 폴더에서 미리보기와 실제 실행을 차례로 해 본 결과입니다.

> python organize.py 시험폴더 --dry-run
이동 0개, 건너뜀 0개 []

(정리기록.log 내용)
2026-10-29 22:00:40  (미리보기) a.png -> 이미지/a.png
2026-10-29 22:00:40  (미리보기) b.pdf -> 문서/b.pdf
2026-10-29 22:00:40  (미리보기) IMG_0001.JPG -> 이미지/IMG_0001.JPG

> python organize.py 시험폴더
이동 3개, 건너뜀 0개 []

기록을 보고 분류가 마음에 들면 --dry-run 없이 실행합니다. 정리된 뒤에도 무엇이 어디로 갔는지 로그에 남아 있어서, 잘못 옮겨진 파일을 되찾기 쉽습니다.

마무리

파일 정리 스크립트는 20줄이면 동작하지만, 매일 무인으로 돌릴 거라면 덮어쓰기와 사용 중인 파일 두 가지는 꼭 막아 두어야 합니다. 둘 다 평소에는 드러나지 않다가 중요한 파일 하나를 잃은 뒤에야 알게 되는 종류의 문제입니다. 처음 며칠은 --dry-run으로 기록만 확인하고, 분류 규칙이 내 다운로드 습관에 맞는지 본 다음 작업 스케줄러에 올리는 순서를 권합니다.