파이썬(Python) 공부 14편
자동화 실전 프로젝트 완전 정복
파이썬을 배운 진짜 이유가 여기 있습니다. 매일 반복하는 파일 정리, 주기적으로 보내는 이메일, 특정 시간마다 실행해야 하는 작업 — 코드 한 번 짜놓으면 파이썬이 대신 해줍니다. 이번 편에서는 os·shutil로 파일을 다루고, smtplib으로 이메일을 보내고, schedule로 작업을 예약하고, watchdog으로 폴더를 감시하는 방법까지 실전 프로젝트 4개와 함께 정리합니다.
🔧 이번 편에서 만들 실전 프로젝트 4가지
개념 설명보다 실제로 써먹을 수 있는 코드가 먼저입니다. 이번 편에서 만드는 프로젝트는 모두 실무에서 바로 활용할 수 있는 것들로 골랐습니다.
확장자별로 파일을 자동 분류해 폴더로 이동. os·shutil·pathlib 활용
엑셀 데이터를 읽어 HTML 이메일로 자동 발송. smtplib·MIMEText 활용
매일 오전 9시, 매주 월요일 등 원하는 시간에 자동 실행. schedule 활용
특정 폴더에 파일이 생기면 자동으로 처리. watchdog 활용
pip install schedule watchdog openpyxl
os·shutil·pathlib·smtplib은 파이썬 표준 라이브러리라 별도 설치 없이 사용 가능합니다.
📁 파일·폴더 자동화 기초 — os·shutil·pathlib
파일 자동화의 3대 도구입니다. os는 경로·디렉터리 조작, shutil은 파일 복사·이동·삭제, pathlib은 객체지향 방식으로 경로를 다루는 현대적인 도구입니다. 세 가지를 조합하면 거의 모든 파일 작업을 자동화할 수 있습니다.
import os, shutil
from pathlib import Path
from datetime import datetime
# ─── pathlib — 현대적 경로 처리 ──────────────
p = Path("C:/Users/me/Downloads") # Windows
p = Path.home() / "Downloads" # 홈 폴더 기준 (Mac/Linux/Win 공통)
print(p.exists()) # 존재 여부
print(p.is_dir()) # 폴더인지
print(p.name) # 'Downloads'
print(p.suffix) # 확장자 (파일일 때)
print(p.stem) # 확장자 제외한 파일명
print(p.parent) # 상위 폴더
# ─── 폴더 내 파일 목록 ───────────────────────
for f in p.iterdir(): # 한 단계만
print(f.name, f.suffix)
for f in p.rglob("*.pdf"): # 하위 폴더까지 재귀 검색
print(f)
# ─── 폴더 생성 ───────────────────────────────
new_dir = p / "이미지"
new_dir.mkdir(exist_ok=True) # 이미 있어도 오류 없음
(p / "문서" / "2026").mkdir(parents=True, exist_ok=True) # 중간 폴더도 자동 생성
# ─── 파일 복사·이동·삭제 ─────────────────────
src = p / "report.xlsx"
dst = p / "문서" / "report.xlsx"
shutil.copy2(src, dst) # 복사 (메타데이터 유지)
shutil.move(str(src), str(dst)) # 이동 (잘라내기+붙여넣기)
src.unlink() # 파일 삭제
shutil.rmtree(str(new_dir)) # 폴더 전체 삭제 (주의!)
# ─── 파일 이름 변경·타임스탬프 붙이기 ────────
for f in p.glob("*.png"):
ts = datetime.now().strftime("%Y%m%d")
new_name = f.parent / f"{ts}_{f.name}"
f.rename(new_name) # 20260401_photo.png
# ─── 파일 크기·수정 시간 확인 ─────────────────
for f in p.iterdir():
if f.is_file():
size = f.stat().st_size / 1024 # KB
mtime = datetime.fromtimestamp(f.stat().st_mtime)
print(f"{f.name:30} {size:.1f}KB {mtime:%Y-%m-%d}")
📊 프로젝트 ① 다운로드 폴더 자동 정리기
다운로드 폴더에 파일이 쌓이다 보면 어느새 수백 개가 됩니다. 확장자별로 폴더를 나눠 자동으로 정리해 주는 스크립트입니다. 한 번만 실행해도 되고, 나중에 스케줄러와 결합해 매일 자동으로 돌릴 수도 있습니다.
import shutil, logging
from pathlib import Path
from datetime import datetime
# ─── 설정 ────────────────────────────────────
DOWNLOAD_DIR = Path.home() / "Downloads"
FOLDER_MAP = {
"이미지" : [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".bmp"],
"동영상" : [".mp4", ".mov", ".avi", ".mkv", ".wmv"],
"음악" : [".mp3", ".wav", ".flac", ".aac"],
"문서" : [".pdf", ".docx", ".doc", ".txt", ".hwp", ".pptx"],
"스프레드시트": [".xlsx", ".xls", ".csv"],
"압축파일": [".zip", ".rar", ".7z", ".tar", ".gz"],
"설치파일": [".exe", ".msi", ".dmg", ".pkg"],
"코드" : [".py", ".js", ".html", ".css", ".json", ".sql"],
}
# ─── 로깅 설정 ───────────────────────────────
logging.basicConfig(
filename="organizer.log",
level=logging.INFO,
format="%(asctime)s %(message)s",
encoding="utf-8",
)
def get_target_folder(suffix: str) -> str:
"""확장자 → 대상 폴더명 반환"""
for folder, exts in FOLDER_MAP.items():
if suffix.lower() in exts:
return folder
return "기타"
def organize():
moved = 0
for file in DOWNLOAD_DIR.iterdir():
if not file.is_file():
continue # 폴더 건너뜀
target_folder = DOWNLOAD_DIR / get_target_folder(file.suffix)
target_folder.mkdir(exist_ok=True)
dest = target_folder / file.name
if dest.exists(): # 같은 이름 파일 있으면 타임스탬프 추가
ts = datetime.now().strftime("%H%M%S")
dest = target_folder / f"{file.stem}_{ts}{file.suffix}"
shutil.move(str(file), str(dest))
logging.info(f"이동: {file.name} → {target_folder.name}/")
moved += 1
print(f"정리 완료: {moved}개 파일 이동됨")
logging.info(f"=== 정리 완료: {moved}개 ===")
if __name__ == "__main__":
organize()
💌 프로젝트 ② 이메일 자동 발송 — smtplib
smtplib은 파이썬 표준 라이브러리로 이메일을 보내는 모듈입니다. Gmail을 사용한다면 앱 비밀번호를 먼저 발급받아야 합니다. (구글 계정 → 보안 → 2단계 인증 활성화 → 앱 비밀번호 생성)
import smtplib, os
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
from pathlib import Path
# ─── 계정 설정 (환경변수 권장) ───────────────
SMTP_HOST = "smtp.gmail.com"
SMTP_PORT = 587
SENDER = os.environ.get("EMAIL_USER", "your@gmail.com")
PASSWORD = os.environ.get("EMAIL_PASS", "앱비밀번호16자리")
def send_report(
to : str | list,
subject : str,
body_html: str,
attachments: list[Path] = None,
):
"""HTML 이메일 + 선택적 첨부파일 발송"""
msg = MIMEMultipart("alternative" if not attachments else "mixed")
msg["From"] = SENDER
msg["To"] = to if isinstance(to, str) else ", ".join(to)
msg["Subject"] = subject
msg.attach(MIMEText(body_html, "html", "utf-8"))
# 첨부파일 처리
for fp in (attachments or []):
part = MIMEBase("application", "octet-stream")
part.set_payload(Path(fp).read_bytes())
encoders.encode_base64(part)
part.add_header("Content-Disposition", f'attachment; filename="{Path(fp).name}"')
msg.attach(part)
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
server.starttls() # TLS 암호화
server.login(SENDER, PASSWORD)
server.sendmail(SENDER, to, msg.as_string())
print(f"발송 완료 → {to}")
# ─── 사용 예시 ───────────────────────────────
if __name__ == "__main__":
html_body = """
<h2>📊 일일 판매 리포트</h2>
<p>오늘 날짜: <b>2026-04-01</b></p>
<table border="1" cellpadding="8">
<tr><th>팀</th><th>판매수</th><th>매출</th></tr>
<tr><td>영업1팀</td><td>142</td><td>21,300,000원</td></tr>
<tr><td>영업2팀</td><td>98</td><td>14,700,000원</td></tr>
</table>
<p>※ 본 메일은 자동 발송입니다.</p>
"""
send_report(
to = "team@company.com",
subject = "[자동] 일일 판매 리포트 - 2026-04-01",
body_html = html_body,
attachments = ["sales_report.xlsx"], # 첨부파일 (없으면 생략)
)
이메일 계정 정보를 코드에 직접 하드코딩하면 GitHub 등에 올렸을 때 노출될 수 있습니다.
- 환경변수 사용: os.environ.get("EMAIL_PASS") 방식 권장
- .env 파일 활용: python-dotenv 라이브러리로 .env 파일에서 로드
- .gitignore에 추가: .env, secrets.py 등 민감 파일은 반드시 Git 제외
⏰ 프로젝트 ③ 스케줄러 — schedule로 작업 예약
schedule 라이브러리는 cron보다 훨씬 직관적인 문법으로 작업을 예약할 수 있습니다. every().day.at("09:00")처럼 영어 문장을 읽듯이 스케줄을 작성합니다.
import schedule, time, logging
from datetime import datetime
logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(message)s")
# ─── 실행할 작업 함수들 ────────────────────────
def daily_report():
logging.info("일일 리포트 이메일 발송 시작")
# send_report(...) ← 위에서 만든 함수 호출
def organize_downloads():
logging.info("다운로드 폴더 정리 시작")
# organize() ← 프로젝트 ① 함수 호출
def backup_files():
logging.info("주간 백업 시작")
def health_check():
print(f"[{datetime.now():%H:%M}] 스케줄러 정상 동작 중")
# ─── 스케줄 등록 ─────────────────────────────
schedule.every().day.at("09:00").do(daily_report) # 매일 오전 9시
schedule.every().day.at("18:30").do(organize_downloads) # 매일 오후 6시 30분
schedule.every(30).minutes.do(health_check) # 30분마다
schedule.every().monday.at("08:00").do(backup_files) # 매주 월요일 8시
schedule.every(10).seconds.do(health_check) # 테스트용: 10초마다
# 특정 날짜·요일 선택지
# schedule.every().hour.do(job) → 매시 정각
# schedule.every().wednesday.do(job) → 매주 수요일
# schedule.every(2).weeks.do(job) → 2주마다
# schedule.every().day.at("12:00").do(job).tag("lunch") → 태그 붙이기
# ─── 실행 루프 ───────────────────────────────
print("스케줄러 시작 — Ctrl+C로 종료")
while True:
schedule.run_pending() # 대기 중인 작업 실행
time.sleep(1) # 1초마다 체크
schedule은 파이썬 프로세스가 실행 중일 때만 작동합니다. 컴퓨터를 켜두거나 서버에서 실행해야 합니다.
- Windows: 작업 스케줄러(Task Scheduler)에 파이썬 스크립트 등록
- Mac / Linux: crontab으로 등록 — crontab -e
- 서버 운영: nohup python scheduler.py & 또는 systemd 서비스 등록
- APScheduler: 더 강력한 스케줄링이 필요하면 APScheduler 라이브러리 활용
👁 프로젝트 ④ watchdog — 폴더 실시간 감시
watchdog은 파일 시스템의 변화(생성·수정·삭제·이동)를 실시간으로 감지하는 라이브러리입니다. 특정 폴더에 파일이 생기는 순간 바로 처리 로직을 실행할 수 있습니다. 스케줄러가 "정해진 시간에" 실행한다면, watchdog은 "파일이 생기는 순간" 즉시 반응합니다.
import time, shutil, logging
from pathlib import Path
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler, FileCreatedEvent
logging.basicConfig(level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s")
class AutoProcessor(FileSystemEventHandler):
"""감시 폴더에 파일이 생기면 자동 처리"""
def __init__(self, output_dir: Path):
self.output_dir = output_dir
self.output_dir.mkdir(exist_ok=True)
def on_created(self, event: FileCreatedEvent):
if event.is_directory:
return
src = Path(event.src_path)
logging.info(f"새 파일 감지: {src.name}")
time.sleep(0.5) # 파일 쓰기 완료 대기
if src.suffix.lower() in (".csv", ".xlsx"):
self._process_data_file(src)
elif src.suffix.lower() in (".jpg", ".png", ".jpeg"):
self._process_image(src)
else:
logging.info(f"처리 규칙 없음: {src.suffix}")
def _process_data_file(self, src: Path):
# 데이터 파일 → 처리 후 output 폴더로 이동
shutil.copy2(src, self.output_dir / src.name)
logging.info(f"데이터 처리 완료: {src.name} → {self.output_dir.name}/")
def _process_image(self, src: Path):
# 이미지 파일 → 이름에 타임스탬프 붙여서 저장
from datetime import datetime
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
dst = self.output_dir / f"{ts}_{src.name}"
shutil.copy2(src, dst)
logging.info(f"이미지 처리 완료: {dst.name}")
def on_deleted(self, event):
if not event.is_directory:
logging.info(f"파일 삭제됨: {Path(event.src_path).name}")
# ─── 감시 시작 ───────────────────────────────
WATCH_DIR = Path("./inbox") # 감시할 폴더
OUTPUT_DIR = Path("./processed") # 처리 결과 저장 폴더
WATCH_DIR.mkdir(exist_ok=True)
event_handler = AutoProcessor(OUTPUT_DIR)
observer = Observer()
observer.schedule(event_handler, str(WATCH_DIR), recursive=False)
observer.start()
print(f"감시 시작: {WATCH_DIR.resolve()} (Ctrl+C 로 종료)")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
🥇 모두 합치기 — 통합 자동화 시스템
위에서 만든 네 가지 기능을 하나로 묶으면 실무 수준의 자동화 시스템이 됩니다. 스케줄러가 매일 정해진 시간에 파일을 정리하고 리포트를 이메일로 보내고, watchdog이 중요 폴더를 항상 감시합니다.
import schedule, time, threading, logging
from pathlib import Path
from watchdog.observers import Observer
# from organize_downloads import organize
# from send_email import send_report
# from watch_folder import AutoProcessor
logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(message)s")
def run_scheduler():
"""스케줄러 — 별도 스레드에서 실행"""
schedule.every().day.at("09:00").do(lambda: logging.info("리포트 발송"))
schedule.every().day.at("18:00").do(lambda: logging.info("폴더 정리"))
while True:
schedule.run_pending()
time.sleep(30)
def run_watchdog():
"""watchdog — 별도 스레드에서 실행"""
from watchdog.events import LoggingEventHandler
handler = LoggingEventHandler()
observer = Observer()
observer.schedule(handler, "./inbox", recursive=False)
observer.start()
try:
while True: time.sleep(1)
except Exception: observer.stop()
observer.join()
if __name__ == "__main__":
# 각 기능을 별도 스레드로 동시 실행
threading.Thread(target=run_scheduler, daemon=True).start()
threading.Thread(target=run_watchdog, daemon=True).start()
print("자동화 시스템 가동 — Ctrl+C 로 종료")
try:
while True: time.sleep(1)
except KeyboardInterrupt:
print("\n종료")
📝 14편 실습 문제
- 프로젝트 ①의 다운로드 정리기를 확장해 날짜별 하위 폴더를 추가로 생성 (이미지/2026-04/ 형식)
- 30일 이상 된 파일은 오래된파일/ 폴더로 이동
- 처리 결과를 organize_log.txt에 기록
- 매일 오전 8시에 현재 날짜·요일·날씨(또는 임의 메시지)를 이메일로 자동 발송
- 발송 성공/실패 여부를 email_log.csv에 날짜·시간과 함께 기록
- 실제 발송 테스트는 자신의 이메일로 먼저 확인
- pathlib 핵심: Path.home() / iterdir() / glob() / mkdir(parents=True) / stat()
- shutil 핵심: copy2() — 복사 / move() — 이동 / rmtree() — 폴더 삭제 (주의)
- 프로젝트 ①: 확장자별 FOLDER_MAP → get_target_folder() → shutil.move() + 충돌 시 타임스탬프
- smtplib 이메일: MIMEMultipart + MIMEText(html) + MIMEBase(첨부) → starttls() → login() → sendmail()
- 보안: 비밀번호는 환경변수·.env로 관리 / .gitignore 등록 필수
- schedule: every().day.at("09:00").do(함수) / run_pending() + time.sleep(1) 루프
- watchdog: FileSystemEventHandler 상속 → on_created/on_deleted/on_modified 오버라이드 → Observer 등록
- 통합: threading.Thread로 스케줄러 + watchdog 동시 실행
가상환경 관리 심화 / 타입 힌트 / 테스트 코드 / 이후 학습 로드맵