파이썬 업무 자동화#6예약 실행 자동화! 매일 정해진 시간에 스크립트 돌리기 (schedule·APScheduler)
목차14개 항목
안녕하세요! Mojangcam입니다 😎
지난 파이썬 업무 자동화 #5 - 로컬 LLM(LM Studio)으로 고객 문의 자동 분류·요약하기 에서는 돈 한 푼 안 들이고 AI에게 문의 정리를 시켜봤어요. 그런데 여기까지 오면 다들 똑같은 벽에 부딪혀요.
"스크립트는 잘 만들었는데… 매일 아침 제가 직접 실행 버튼을 눌러야 하네요?" 😅
자동화인데 사람이 실행시키면 반쪽짜리잖아요. 그래서 이번 편에서는 파이썬 스크립트가 정해진 시간에 알아서 돌아가게 만들어볼게요. 매일 아침 9시 리포트 자동 생성, 10분마다 서버 상태 체크, 평일 퇴근 시간에만 일일 정산 — 전부 코드 몇 줄이면 끝나요!

📌 준비물
- Python 3.8 이상 (작성 기준: Python 3.14.x)
- schedule 1.2.x — 사람이 읽기 쉬운 문법의 초간단 스케줄러
- APScheduler 3.11.x — cron 표현식·요일 조건까지 필요할 때 (3단계에서 사용)
- python-dotenv 1.x — 웹훅 URL 같은 비밀값을
.env에서 불러오기 - requests 2.x — 디스코드 웹훅 전송용
pip install schedule APScheduler python-dotenv requests💡
schedule에서 타임존(Asia/Seoul)을 지정하려면pytz도 함께 설치해주세요 (pip install pytz).
🧠 왜 파이썬으로 예약 실행을 하나요?
윈도우 작업 스케줄러나 리눅스 cron을 써도 되는데 굳이 파이썬으로 하는 이유가 있어요.
- 한 파일로 끝나요 : 스크립트 안에 스케줄 규칙이 같이 들어 있어서, 다른 PC로 옮겨도 그대로 동작해요.
- 조건을 마음대로 걸 수 있어요 : "공휴일이면 건너뛰기", "실패하면 5분 뒤 재시도" 같은 로직을 파이썬 문법으로 그냥 짜면 돼요.
- 로그를 내가 원하는 대로 남겨요 : 실행 기록을 디스코드로 바로 쏠 수도 있죠.
반대로 PC를 껐다 켜도 자동으로 살아나야 한다면 OS 스케줄러가 유리해요. 이건 아래 보너스 파트에서 같이 정리할게요!
📌 1단계 - schedule로 "매일 9시" 만들기
먼저 schedule 라이브러리의 기본 문법부터 볼게요. 영어 문장 읽듯이 읽히는 게 이 라이브러리의 매력이에요.
# schedule 1.2.x / Python 3.8+
import time
import schedule
def morning_report():
print("[리포트] 오늘의 업무 리포트를 생성했어요!")
# 매일 오전 9시
schedule.every().day.at("09:00").do(morning_report)
# 10분마다
schedule.every(10).minutes.do(morning_report)
# 매주 월요일 08:30
schedule.every().monday.at("08:30").do(morning_report)
while True:
schedule.run_pending() # 실행할 때가 된 작업이 있으면 실행
time.sleep(1) # 1초 쉬었다 다시 확인하나씩 뜯어볼게요 💡
- schedule.every() : "매 ~마다"의 시작점이에요. 뒤에
.day/.hour/.monday같은 단위를 붙여요. - .at("09:00") : 실행 시각.
"HH:MM"또는"HH:MM:SS"형식이고,.every().hour에 붙일 땐":30"처럼 분만 적어요. - .do(함수) : 실행할 함수를 등록해요. ⚠️ 괄호를 붙이면 안 돼요.
do(morning_report())라고 쓰면 지금 당장 실행돼버려요! - run_pending() : 등록된 작업 중 "지금 실행할 때가 된" 것만 골라 실행해요. 이 줄을 반복문 안에서 계속 호출해줘야 스케줄이 동작해요.
- time.sleep(1) : 이게 없으면 CPU를 100% 태워요. 분 단위 작업이라면
sleep(30)정도도 충분해요.
타임존을 확실히 하고 싶다면 이렇게 써요.
schedule.every().day.at("09:00", "Asia/Seoul").do(morning_report)📌 2단계 - 실전! 매일 아침 리포트를 디스코드로 보내기
이제 진짜 쓸모 있는 걸 만들어볼게요. 업무 자동화 #4 - 디스코드 웹훅으로 알림 보내기 에서 만든 웹훅을 재활용해서, 매일 아침 9시에 오늘 할 일 요약을 디스코드로 쏘는 자동화예요.
먼저 프로젝트 폴더에 .env 파일을 만들어요.
# .env (절대 깃허브에 올리지 마세요!)
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/여기에_본인_웹훅_주소
REPORT_TIME=09:00그리고 파이썬에서 이렇게 불러와요.
# Python 3.8+ / python-dotenv 1.x
import os
from dotenv import load_dotenv
load_dotenv() # .env 파일을 읽어서 환경변수로 올려줘요
WEBHOOK_URL = os.getenv("DISCORD_WEBHOOK_URL")
REPORT_TIME = os.getenv("REPORT_TIME", "09:00") # 없으면 09:00 사용하나씩 뜯어볼게요 💡
- load_dotenv() :
.env파일을 찾아서 값을 환경변수로 등록해요. 이 줄이os.getenv()보다 먼저 나와야 해요. - os.getenv("키", "기본값") : 값이 없을 때 쓸 기본값을 두 번째 인자로 줄 수 있어요.
- 웹훅 주소나 토큰은 코드에 직접 적지 마세요. 실수로 깃허브에 올라가면 남이 마음대로 쓸 수 있어요.
💡 꼭 하세요:
.gitignore파일에.env한 줄을 추가해두면 실수로 커밋되는 사고를 막을 수 있어요.
📌 3단계 - 평일에만, cron처럼 정교하게 (APScheduler)
schedule은 쉬운 대신 "평일 09:00", "매월 1일" 같은 조건이 약해요. 이럴 땐 APScheduler의 CronTrigger가 답이에요.
# APScheduler 3.11.x / Python 3.8+
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger
def weekday_report():
print("평일 아침에만 실행돼요!")
sched = BlockingScheduler(timezone="Asia/Seoul")
# 월~금 오전 9시 정각
sched.add_job(weekday_report, CronTrigger(day_of_week="mon-fri", hour=9, minute=0))
# 매월 1일 00:10
sched.add_job(weekday_report, CronTrigger(day=1, hour=0, minute=10))
sched.start() # 여기서 프로그램이 멈춰서 계속 대기해요하나씩 뜯어볼게요 💡
- BlockingScheduler : 이 스케줄러가 프로그램을 붙잡고(block) 계속 돌아요. 스케줄만 돌리는 단독 스크립트에 딱이에요.
- timezone="Asia/Seoul" : 서버가 해외(UTC)에 있어도 한국 시간 기준으로 돌게 해줘요. VPS에 올릴 거면 필수!
- CronTrigger(day_of_week="mon-fri", hour=9) : cron 표현식을 파이썬 인자로 쓴 거예요.
day_of_week는mon,tue,...또는0~6을 받아요. - 디스코드 봇 안에서 같이 돌리고 싶다면
BlockingScheduler대신 AsyncIOScheduler를 쓰면 돼요.
📌 전체 코드 (복붙 OK)
# daily_report.py
# schedule 1.2.x / python-dotenv 1.x / requests 2.x / Python 3.8+
import os
import time
import logging
from datetime import datetime
import requests
import schedule
from dotenv import load_dotenv
load_dotenv()
WEBHOOK_URL = os.getenv("DISCORD_WEBHOOK_URL")
REPORT_TIME = os.getenv("REPORT_TIME", "09:00")
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.FileHandler("scheduler.log", encoding="utf-8"),
logging.StreamHandler(),
],
)
def send_discord(message: str) -> None:
"""디스코드 웹훅으로 메시지를 보냅니다."""
if not WEBHOOK_URL:
logging.warning(".env에 DISCORD_WEBHOOK_URL이 없어요. 전송을 건너뜁니다.")
return
res = requests.post(WEBHOOK_URL, json={"content": message}, timeout=10)
res.raise_for_status()
def build_report() -> str:
"""실제 업무 로직 자리. 엑셀 집계·크롤링 결과 등으로 바꿔 쓰세요."""
today = datetime.now().strftime("%Y-%m-%d (%a)")
return (
f"**📋 {today} 업무 리포트**\n"
f"- 처리 대기 건: 12건\n"
f"- 어제 완료: 8건\n"
f"- 생성 시각: {datetime.now().strftime('%H:%M:%S')}"
)
def safe_job() -> None:
"""에러가 나도 스케줄러가 죽지 않도록 감싸주는 래퍼입니다."""
try:
send_discord(build_report())
logging.info("리포트 전송 성공")
except Exception as e:
logging.exception("리포트 전송 실패: %s", e)
def main() -> None:
schedule.every().day.at(REPORT_TIME).do(safe_job)
schedule.every(30).minutes.do(
lambda: logging.info("스케줄러 생존 확인 (heartbeat)")
)
logging.info("스케줄러를 시작합니다. 매일 %s에 리포트를 보냅니다.", REPORT_TIME)
logging.info("등록된 작업: %s", schedule.get_jobs())
while True:
schedule.run_pending()
time.sleep(1)
if __name__ == "__main__":
main()실행은 이렇게 해요.
python daily_report.py📌 실행 결과
터미널에 이런 로그가 찍히고, 지정한 시간이 되면 디스코드에 리포트가 도착해요.
2026-08-15 08:59:31 [INFO] 스케줄러를 시작합니다. 매일 09:00에 리포트를 보냅니다.
2026-08-15 08:59:31 [INFO] 등록된 작업: [Every 1 day at 09:00:00 do safe_job() ...]
2026-08-15 09:00:00 [INFO] 리포트 전송 성공
2026-08-15 09:29:31 [INFO] 스케줄러 생존 확인 (heartbeat)
scheduler.log 파일에도 같은 내용이 그대로 쌓이니, 나중에 "어제 진짜 돌았나?"를 확인할 때 아주 유용해요 👍
💡 보너스 - PC를 껐다 켜도 살아나게 하기
파이썬 스크립트는 터미널을 닫으면 같이 죽어요. 완전 무인 운영을 하려면 OS에 맡기는 게 좋아요.
윈도우 (작업 스케줄러)
Win + R→taskschd.msc입력- 작업 만들기 → 트리거: "시스템 시작 시" 또는 "매일"
- 동작: 프로그램은
python.exe의 전체 경로, 인수는daily_report.py, 시작 위치에 스크립트 폴더 경로를 꼭 넣기
리눅스·라즈베리파이 (systemd)
sudo nano /etc/systemd/system/daily-report.service[Unit]
Description=Daily Report Scheduler
After=network-online.target
[Service]
WorkingDirectory=/home/pi/report
ExecStart=/usr/bin/python3 /home/pi/report/daily_report.py
Restart=always
[Install]
WantedBy=multi-user.targetsudo systemctl enable --now daily-reportRestart=always 덕분에 스크립트가 죽어도 자동으로 다시 살아나요. 자세한 배포 방법은 파이썬 VPS 배포 입문 편에서 다뤘어요!
📌 자주 나는 에러
1. 시간이 됐는데 아무 일도 안 일어나요
원인 : while True 안에서 schedule.run_pending()을 호출하지 않았거나, 스크립트가 이미 종료된 경우예요.
해결 : 반복문이 살아 있는지, 터미널이 계속 켜져 있는지 확인하세요. .at("9:00")처럼 한 자리 시(時)를 쓰면 안 돼요 — 반드시 "09:00"으로 두 자리를 맞춰주세요.
2. ScheduleValueError: Invalid time format for a daily job (valid format is HH:MM(:SS)?)
원인 : .at()에 넘긴 문자열 형식이 틀렸어요. "09시", "9:0", "09:00 AM" 전부 안 돼요.
해결 : "09:00" 또는 "09:00:00" 형태로만 적어주세요. .env에서 읽어올 땐 앞뒤 공백도 조심하고요.
3. TypeError: the first argument must be callable
원인 : .do(job())처럼 괄호를 붙여서 함수를 실행한 결과를 넘긴 거예요.
해결 : .do(job)으로 함수 이름만 넘기세요. 인자를 주고 싶으면 .do(job, "인자값") 또는 .do(lambda: job("인자값"))을 쓰면 돼요.
4. 서버(VPS)에 올렸더니 9시가 아니라 18시에 실행돼요
원인 : 해외 서버의 기본 시간대가 UTC라서 9시간 차이가 나요.
해결 : APScheduler는 BlockingScheduler(timezone="Asia/Seoul"), schedule은 .at("09:00", "Asia/Seoul")로 타임존을 명시하세요. 서버 자체를 바꾸려면 sudo timedatectl set-timezone Asia/Seoul도 방법이에요.
5. 작업 하나가 에러 나니까 스케줄러 전체가 멈춰버려요
원인 : .do()에 등록한 함수에서 예외가 터지면 그대로 위로 전파돼요.
해결 : 위 전체 코드의 safe_job()처럼 try/except로 감싸세요. 로그만 남기고 다음 스케줄은 계속 돌아가게 하는 게 핵심이에요.
💬 schedule vs APScheduler, 뭘 쓸까요?
| 상황 | 추천 |
|---|---|
| "매일 9시", "10분마다" 정도면 충분 | schedule (문법이 가장 쉬움) |
| 평일만·매월 1일·cron 표현식 필요 | APScheduler |
| 재부팅해도 예약이 남아 있어야 함 | APScheduler + JobStore(DB) |
| 디스코드 봇 안에서 주기 실행 | discord.py의 tasks.loop |
처음이라면 schedule로 시작해서, 조건이 복잡해지는 순간 APScheduler로 갈아타는 흐름을 추천해요!
📌 마무리
오늘은 파이썬 스크립트를 정해진 시간에 알아서 돌게 만들어봤어요. 정리하면 이래요.
schedule.every().day.at("09:00").do(job)+while True: run_pending()조합이 기본형- 웹훅·토큰 같은 비밀값은
.env+python-dotenv로 분리 - 작업 함수는 try/except로 감싸서 스케줄러가 죽지 않게
- 평일·매월 조건이나 타임존이 중요하면 APScheduler CronTrigger
- 무인 운영은 윈도우 작업 스케줄러 / 리눅스 systemd에 맡기기
다음 편인 파이썬 업무 자동화 #7 - 동적 페이지·로그인 자동화 (Selenium) 에서는 로그인이 필요한 사이트에서 자료를 자동으로 긁어오는 방법을 다뤄볼게요. requests로는 안 잡히던 그 페이지들이요! 기대해주세요 🙌
오늘 만든 스케줄러에 어떤 작업을 물려두셨는지 댓글로 알려주세요. 다음 글감으로 만들어볼게요 😎
📚 파이썬 업무 자동화 시리즈
- #1 - 엑셀 자동화 입문 (openpyxl)
- #2 - 웹 크롤링 입문 (requests + BeautifulSoup)
- #3 - 파일 정리 자동화 (이름 일괄 변경)
- #4 - 디스코드 웹훅(Webhook)으로 알림 보내기
- #5 - 로컬 LLM(LM Studio)으로 고객 문의 자동 분류·요약
- #6 - 예약 실행 자동화 (schedule·APScheduler) ← 지금 이 글!
- #7 - 동적 페이지·로그인 자동화 (Selenium) (예정)
번외: 파이썬 이메일 자동 발송하기 - 지메일(Gmail) 자동화 (smtplib)
연재 · 14편 중 6번째
파이썬 업무 자동화
- #1엑셀 자동화 입문 (openpyxl)!
- #2웹 크롤링 입문 (requests + BeautifulSoup)!
- #3파일 정리 자동화 (이름 일괄 변경)!
- #4디스코드 웹훅(Webhook)으로 알림 보내기 (requests)!
- #5로컬 LLM(LM Studio)으로 고객 문의 자동 분류·요약하기! (무료 AI)
- #6예약 실행 자동화! 매일 정해진 시간에 스크립트 돌리기 (schedule·APScheduler)지금 읽는 글
- #7동적 페이지·로그인 자동화! requests로 안 되는 사이트 긁기 (Selenium)
- #8PDF 자동 처리! 여러 파일 병합·분할부터 비밀번호까지 (pypdf) 📄
- #9마우스·키보드 GUI 자동화! 클릭·타이핑을 파이썬이 대신하게 하기 (PyAutoGUI)
- #10워드(Word) 문서 자동 생성! 엑셀 명단으로 계약서·안내문 100장 뽑기 (python-docx) 📄
- #11개인화 대량 메일 자동 발송! 명단별 첨부파일·발송 로그·재시도까지 (smtplib 실전편) 📬
- #12구글 시트 연동하기! 팀원과 같이 보는 자동 시트 (gspread) 📗
- #13파이썬 자동화 로그·재시도·디스코드 실패 알림 만들기 - 조용히 멈추는 스크립트 잡기
- #14파워포인트(PowerPoint) 보고서 자동 생성! 엑셀 숫자만 바꾸면 발표자료 완성 (python-pptx) 📊
