본문 바로가기
카테고리 없음

파이썬으로 특정 시간에 프로그램 자동 실행하기 (schedule 라이브러리 실전)

by First Adventure 2026. 4. 20.
반응형

코드는 만들었는데, 실행은 매번 손으로 하고 있다면.

  데이터 수집, 파일 정리, 이메일 발송. 자동화 스크립트는 만들었는데 결국 매일 직접 실행하고 있다면, 자동화가 반쪽짜리인 셈입니다. 진짜 자동화는 실행까지 자동으로 되어야 완성됩니다.

  이 글에서는 파이썬 schedule 라이브러리로 특정 시간, 특정 요일, 일정 간격마다 코드를 자동 실행하는 방법을 다룹니다. 매일 오전 9시, 매주 월요일, 10분마다 같은 조건을 코드 한 줄로 설정할 수 있습니다.

 

사전 준비

필요한 라이브러리 설치

pip install schedule
  • schedule: 사람이 읽기 쉬운 문법으로 반복 실행 일정을 등록하는 라이브러리입니다. 윈도우 작업 스케줄러나 cron 없이 파이썬 코드 안에서 모든 스케줄을 관리할 수 있습니다.

 

Case 1. 기본 사용법 — 간격·시간·요일 설정

  schedule의 문법은 영어 문장을 읽듯이 직관적입니다. 자주 쓰는 패턴을 한 곳에 모아 설명합니다.

# basic_schedule.py
import schedule
import time
from datetime import datetime

def job(name: str = "작업"):
    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    print(f"[{now}] {name} 실행됨")

# ── 간격 기반 ──────────────────────────────
schedule.every(10).seconds.do(job, name="10초마다")       # 10초마다
schedule.every(5).minutes.do(job, name="5분마다")         # 5분마다
schedule.every(1).hours.do(job, name="1시간마다")         # 1시간마다
schedule.every().day.do(job, name="매일")                 # 매일 (실행 시각 기준)

# ── 특정 시각 ──────────────────────────────
schedule.every().day.at("09:00").do(job, name="매일 오전 9시")
schedule.every().day.at("18:30").do(job, name="매일 오후 6시 30분")

# ── 요일 지정 ──────────────────────────────
schedule.every().monday.at("09:00").do(job, name="매주 월요일 오전 9시")
schedule.every().friday.at("17:00").do(job, name="매주 금요일 오후 5시")
# 요일: monday / tuesday / wednesday / thursday / friday / saturday / sunday

# ── 실행 루프 ──────────────────────────────
print("스케줄러 시작. 종료하려면 Ctrl+C")
while True:
    schedule.run_pending()  # 실행 시각이 된 작업 실행
    time.sleep(1)           # 1초 간격으로 확인

 

Case 2. 여러 작업을 동시에 등록하기

  스케줄은 여러 개를 동시에 등록할 수 있습니다. 각 작업이 서로 다른 시간에 독립적으로 실행됩니다.

# multi_schedule.py
import schedule
import time
from datetime import datetime

def collect_data():
    print(f"[{datetime.now():%H:%M:%S}] 데이터 수집 실행")
    # 실제로는 API 호출, 파일 읽기 등

def send_report():
    print(f"[{datetime.now():%H:%M:%S}] 보고서 발송 실행")
    # 실제로는 이메일 발송, 구글 시트 업데이트 등

def cleanup_files():
    print(f"[{datetime.now():%H:%M:%S}] 임시 파일 정리 실행")
    # 실제로는 폴더 정리 스크립트 호출

# 각 작업에 다른 스케줄 등록
schedule.every(30).minutes.do(collect_data)          # 30분마다 데이터 수집
schedule.every().day.at("08:00").do(send_report)     # 매일 오전 8시 보고서 발송
schedule.every().sunday.at("23:00").do(cleanup_files) # 매주 일요일 밤 파일 정리

print(f"등록된 작업 수: {len(schedule.jobs)}개")
for job in schedule.jobs:
    print(f"  다음 실행: {job.next_run}  |  {job}")

while True:
    schedule.run_pending()
    time.sleep(1)

 

Case 3. 작업 실패 시 오류를 잡고 계속 실행하기

  스케줄러의 가장 큰 함정은 작업 하나가 오류로 멈추면 이후 모든 스케줄도 멈춰버린다는 점입니다. 오류가 나도 스케줄러는 계속 돌아가도록 예외 처리를 반드시 추가해야 합니다.

# safe_schedule.py
import schedule
import time
import traceback
import logging
from datetime import datetime
from functools import wraps

# 로그 설정 (오류 내역을 파일에 기록)
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler("scheduler.log", encoding="utf-8"),
        logging.StreamHandler()  # 콘솔에도 출력
    ]
)
logger = logging.getLogger(__name__)

def safe_job(func):
    """작업 실패 시 오류를 기록하고 스케줄러는 계속 실행하는 데코레이터"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            func(*args, **kwargs)
        except Exception as e:
            logger.error(f"{func.__name__} 실행 중 오류 발생: {e}")
            logger.error(traceback.format_exc())
    return wrapper

@safe_job
def risky_job():
    """오류가 날 수 있는 작업"""
    logger.info("작업 시작")
    # 예: 네트워크 오류, 파일 없음, API 한도 초과 등
    raise ConnectionError("서버에 연결할 수 없습니다")  # 테스트용

@safe_job
def stable_job():
    """안정적으로 실행되는 작업"""
    logger.info("안정적인 작업 완료")

schedule.every(5).seconds.do(risky_job)
schedule.every(10).seconds.do(stable_job)

logger.info("스케줄러 시작")
while True:
    schedule.run_pending()
    time.sleep(1)

 

Case 4. 한 번만 실행하고 스케줄에서 제거하기

  반복 실행이 아니라 특정 조건에서 딱 한 번만 실행하고 스케줄을 자동으로 취소하는 방법입니다.

# once_schedule.py
import schedule
import time

def job_once():
    print("딱 한 번만 실행됩니다")
    return schedule.CancelJob  # 이 값을 반환하면 해당 스케줄이 자동 제거됨

# 30초 후 한 번만 실행
schedule.every(30).seconds.do(job_once)

# 특정 시각에 한 번만 실행
def send_once():
    print("오전 9시에 딱 한 번 발송")
    return schedule.CancelJob

schedule.every().day.at("09:00").do(send_once)

while True:
    schedule.run_pending()
    time.sleep(1)

 

실전에서 바로 쓰는 완성 코드

  지금까지 작성한 시리즈 스크립트(엑셀 합치기, 이메일 발송, 폴더 정리 등)를 스케줄에 등록해 완전 자동화하는 실전 완성본입니다. 설정 파일로 스케줄을 관리하고, 오류는 로그로 기록합니다.

실행 예시

python scheduler.py
# scheduler.py
import schedule
import time
import logging
import traceback
import subprocess
from functools import wraps
from datetime import datetime

# ── 로그 설정 ──────────────────────────────
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler("scheduler.log", encoding="utf-8"),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# ── 실행할 스크립트 경로 ────────────────────
SCRIPTS = {
    "excel_merge":   "merge.py",          # 엑셀 합치기
    "send_email":    "send.py",           # 이메일 발송
    "folder_clean":  "organize.py",       # 폴더 정리
    "gsheet_update": "gsheet.py",         # 구글 시트 업데이트
}

# ── 안전 실행 데코레이터 ────────────────────
def safe_job(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            logger.info(f"{func.__name__} 시작")
            func(*args, **kwargs)
            logger.info(f"{func.__name__} 완료")
        except Exception as e:
            logger.error(f"{func.__name__} 실패: {e}")
            logger.error(traceback.format_exc())
    return wrapper

# ── 외부 스크립트 실행 함수 ─────────────────
def run_script(script_name: str, args: list = None):
    """지정한 파이썬 스크립트를 서브프로세스로 실행"""
    cmd = ["python", script_name] + (args or [])
    result = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8")
    if result.returncode != 0:
        raise RuntimeError(f"스크립트 오류:\n{result.stderr}")
    return result.stdout

# ── 작업 정의 ───────────────────────────────
@safe_job
def job_excel_merge():
    output = run_script(
        SCRIPTS["excel_merge"],
        ["--folder", "excel_files", "--output", "merged_result.xlsx"]
    )
    logger.info(output.strip())

@safe_job
def job_send_email():
    output = run_script(
        SCRIPTS["send_email"],
        ["--csv", "recipients.csv", "--subject", "자동 발송 보고서"]
    )
    logger.info(output.strip())

@safe_job
def job_folder_clean():
    output = run_script(
        SCRIPTS["folder_clean"],
        ["--folder", "downloads", "--mode", "both"]
    )
    logger.info(output.strip())

@safe_job
def job_gsheet_update():
    output = run_script(
        SCRIPTS["gsheet_update"],
        ["--url", "스프레드시트_URL", "--mode", "append", "--row", f"{datetime.now():%Y-%m-%d},자동업데이트,완료"]
    )
    logger.info(output.strip())

# ── 스케줄 등록 ─────────────────────────────
def setup_schedules():
    schedule.every().day.at("08:00").do(job_excel_merge)       # 매일 오전 8시
    schedule.every().day.at("08:30").do(job_send_email)        # 매일 오전 8시 30분
    schedule.every().sunday.at("23:00").do(job_folder_clean)   # 매주 일요일 밤
    schedule.every(1).hours.do(job_gsheet_update)              # 1시간마다

    logger.info(f"등록된 스케줄 수: {len(schedule.jobs)}개")
    for job in schedule.jobs:
        logger.info(f"  다음 실행: {job.next_run}  |  {job}")

# ── 메인 루프 ───────────────────────────────
def main():
    logger.info("=" * 50)
    logger.info("스케줄러 시작")
    logger.info("=" * 50)

    setup_schedules()

    while True:
        schedule.run_pending()
        time.sleep(1)

if __name__ == "__main__":
    main()

 

스케줄러를 백그라운드에서 계속 실행하는 방법

  schedule은 스크립트가 실행 중인 동안만 작동합니다. 터미널을 닫으면 멈춥니다. 컴퓨터를 켜두는 동안 계속 실행하려면 아래 방법 중 하나를 선택하세요.

Windows — 작업 스케줄러에 등록

# 1. 시작 메뉴에서 "작업 스케줄러" 검색 후 실행
# 2. 작업 만들기 → 트리거: 컴퓨터 시작 시
# 3. 동작: 프로그램 시작 → python.exe 선택
# 4. 인수 추가: scheduler.py 전체 경로 입력
# 예: C:\Users\사용자이름\projects\scheduler.py

Windows — 백그라운드 실행 (.bat 파일)

:: run_scheduler.bat
@echo off
cd /d C:\Users\사용자이름\projects
start /B pythonw scheduler.py
:: pythonw는 콘솔 창 없이 백그라운드로 실행

Mac / Linux — nohup으로 백그라운드 실행

# 백그라운드로 실행 (터미널 닫아도 계속 동작)
nohup python scheduler.py > scheduler.log 2>&1 &

# 실행 중인 프로세스 확인
ps aux | grep scheduler.py

# 종료
kill $(pgrep -f scheduler.py)

 

자주 발생하는 문제와 해결법

문제 1. 등록한 시각이 지나도 실행이 안 될 때

  schedule은 등록 시점 이후의 다음 실행 시각부터 작동합니다. 오전 9시에 등록했는데 이미 9시가 지났다면 다음 날 9시에 실행됩니다. 즉시 한 번 실행하고 싶다면 등록 후 수동으로 한 번 호출하세요.

<code">job_func = job_excel_merge
schedule.every().day.at("09:00").do(job_func)
job_func()  # 등록과 동시에 즉시 한 번 실행

 

문제 2. 작업 실행 시간이 길어서 다음 스케줄이 밀릴 때

  작업 하나가 오래 걸리면 그동안 다른 스케줄도 대기합니다. 오래 걸리는 작업은 별도 스레드로 실행해 스케줄러가 막히지 않도록 합니다.

import threading

def run_in_thread(func):
    """작업을 별도 스레드로 실행하는 래퍼"""
    def wrapper():
        thread = threading.Thread(target=func)
        thread.start()
    return wrapper

@run_in_thread
def long_running_job():
    time.sleep(60)  # 오래 걸리는 작업
    print("완료")

schedule.every(10).minutes.do(long_running_job)

 

문제 3. 스케줄을 동적으로 취소하거나 변경하고 싶을 때

  등록된 스케줄은 태그를 붙여 관리하면 특정 작업만 골라서 취소할 수 있습니다.

# 태그를 붙여 등록
schedule.every().day.at("09:00").do(job_excel_merge).tag("excel")
schedule.every().day.at("08:30").do(job_send_email).tag("email")

# 특정 태그의 작업만 취소
schedule.get_jobs("excel")  # excel 태그 작업 목록 확인
for job in schedule.get_jobs("excel"):
    schedule.cancel_job(job)

# 전체 스케줄 취소
schedule.clear()  # 인수 없이 쓰면 전체 취소

 

마치며..

  오늘은 schedule 라이브러리로 파이썬 스크립트를 원하는 시각에 자동 실행하는 방법을 살펴보았습니다. 기술의 목적은 결국 인간의 시간을 더 가치 있는 곳에 쓰게 하는 것입니다. 스크립트를 실행하는 일조차 자동화하면, 파이썬이 알아서 돌아가는 동안 여러분은 다른 일에 집중할 수 있습니다.

  이 글의 완성 코드는 지금까지 시리즈에서 만든 엑셀 합치기, 이메일 발송, 폴더 정리, 구글 시트 업데이트 스크립트를 모두 하나의 스케줄러에 연결하는 구조로 작성했습니다. 각 스크립트를 아직 만들지 않으셨다면 이전 글들을 먼저 참고해 보세요.

 

관련 내용

 


본 포스팅의 코드는 자유롭게 수정 및 배포가 가능하나, 상업적 이용 시에는 출처를 반드시 밝혀주시기 바랍니다.

 

반응형