AI한테 맡겨봤습니다 서재로
입문 · 무료

첫 자동화 하나

매주 손으로 하던 일 하나를 골라, 내가 없어도 정해진 시간에 돌게 만드는 것까지. 여기까지가 30분입니다.

읽고 따라 하는 순서대로 적었습니다. 중간에 건너뛰지 마세요 — 5단계가 제일 중요합니다.

STEP 0

무엇을 고르나

처음부터 어려운 걸 고르면 반드시 중간에 멈춥니다. 조건 셋을 다 만족하는 일을 하나만 고르세요.

예를 들면 이런 것들입니다. 매일 아침 몇 개 사이트에서 새 글 제목을 모아 파일로 남기기, 매주 엑셀 하나를 열어 숫자를 뽑아 요약 만들기, 매일 폴더를 훑어 어제 만든 파일 목록을 정리하기.

이 문서에서 만들 것

예제로는 매일 아침 관심 있는 사이트들에서 새 글 제목을 모아 파일로 남기는 일을 씁니다. 짧고, 안전하고, 실제로 쓸모가 있어서 첫 자동화로 적당합니다. 여러분 일로 바꿔도 뼈대는 그대로입니다.


STEP 1

손으로 하던 일을 그대로 코드로 옮긴다

여기서 잘 만들려고 하지 마세요. 지금 손으로 하는 순서를 그대로 옮기기만 합니다. 외부 라이브러리도 안 씁니다.

daily/collect.py
"""매일 아침 새 글 제목을 모아 파일로 남긴다."""
import random, time, urllib.request
from datetime import date
from pathlib import Path
from xml.etree import ElementTree

D = Path(__file__).resolve().parent
OUT = D.parent / "data"
OUT.mkdir(exist_ok=True)

FEEDS = [
    # 여러분이 매일 들여다보는 곳으로 바꾸세요
    "https://example.com/rss.xml",
]

UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
     "(KHTML, like Gecko) Chrome/126.0 Safari/537.36"


def fetch(url):
    req = urllib.request.Request(url, headers={"User-Agent": UA})
    with urllib.request.urlopen(req, timeout=15) as r:
        return r.read()


def main():
    lines, dead = [], 0
    for f in FEEDS:
        try:
            root = ElementTree.fromstring(fetch(f))
            for it in root.iter("item"):
                t = (it.findtext("title") or "").strip()
                u = (it.findtext("link") or "").strip()
                if t:
                    lines.append(f"{t}\t{u}")
        except Exception as e:
            dead += 1
            print(f"[skip] {f} — {e}")     # 한 곳이 죽어도 계속 간다
        time.sleep(random.uniform(1.2, 2.6))

    out = OUT / f"{date.today():%Y-%m-%d}.txt"
    out.write_text("\n".join(lines), encoding="utf-8")
    print(f"{len(lines)}건 저장 → {out}  (실패한 곳 {dead})")


if __name__ == "__main__":
    main()

실행해서 파일이 생기는지만 확인하세요. 여기까지가 자동화가 아니라 도구입니다.

터미널
python daily/collect.py
여기서 이미 한 가지를 했습니다

수집처마다 try로 감싸고 실패 개수를 찍은 것. 이게 없으면 사이트 한 곳이 응답을 안 하는 날 전체가 멈추고, 아무 일도 안 일어났다는 걸 며칠 뒤에 압니다. 조용한 실패가 자동화에서 제일 비쌉니다.


STEP 2

정해진 시간에 스스로 돌게 한다

이제 자동화가 됩니다. 윈도우 기준으로 적습니다.

작업 등록 (관리자 권한 없이)
schtasks /create /tn "MY\collect" ^
  /tr "\"C:\Python312\pythonw.exe\" \"C:\myauto\daily\collect.py\"" ^
  /sc daily /st 08:30 /f
여기서 대부분 한 번씩 넘어집니다

python.exe가 아니라 pythonw.exe로 등록하세요. 그냥 python으로 걸면 실행될 때마다 검은 창이 튀어나와 하던 작업의 포커스를 뺏고, 무심코 닫으면 그날 것이 죽습니다.

그리고 작업 속성에서 "예약 시간을 놓친 경우 가능한 한 빨리 작업 시작"을 켜세요. 안 켜면 그 시간에 컴퓨터가 꺼져 있거나 업데이트로 재부팅 중이었을 때 그날은 통째로 건너뜁니다.

맥이라면

launchd를 씁니다. ~/Library/LaunchAgents/에 plist 하나를 두고 launchctl load. 개념은 같습니다 — 정해진 시각에 인터프리터가 스크립트를 부르는 것.


STEP 3

인자는 스크립트 안에서 세운다

스케줄러는 여러분의 환경변수도, 작업 폴더도 물려주지 않습니다. 손으로 실행할 땐 되던 게 예약으로 돌리면 안 되는 이유가 거의 이것입니다.

daily/run.py — 진입점 하나만 등록한다
"""스케줄러가 부르는 단 하나의 파일. 환경은 여기서 다 세운다."""
import os, subprocess, sys
from pathlib import Path

D = Path(__file__).resolve().parent
os.chdir(D.parent)                                  # 작업 폴더 고정
os.environ.setdefault("PYTHONIOENCODING", "utf-8")  # 한글 출력이 깨지지 않게

r = subprocess.run([sys.executable, "daily/collect.py"],
                   capture_output=True, text=True,
                   encoding="utf-8", errors="replace")
print(r.stdout, r.stderr)
sys.exit(r.returncode)

등록도 이 파일 하나로 바꿉니다. 스케줄러에 넘기는 명령줄에는 길이 제한이 있어서, 인자가 늘어나면 조용히 잘립니다. 진입점을 하나로 두면 그 문제를 아예 안 만납니다.


STEP 4

돌았는지 확인하는 방법을 만든다

예약을 걸어두고 다음 날 아침에 파일을 열어보는 건 확인이 아닙니다. 며칠은 그렇게 되다가, 안 보게 되고, 그 사이에 죽습니다.

daily/check.py
"""오늘 것이 실제로 생겼는지 본다. 안 생겼으면 다시 돌린다."""
import subprocess, sys
from datetime import date
from pathlib import Path

D = Path(__file__).resolve().parent
EVIDENCE = D.parent / "data" / f"{date.today():%Y-%m-%d}.txt"


def ok():
    # 파일이 있는 것만으로는 부족하다. 내용이 있어야 성공이다.
    return EVIDENCE.exists() and EVIDENCE.stat().st_size > 0


if ok():
    print("OK")
else:
    print("오늘 것이 없다 — 다시 돌린다")
    subprocess.run([sys.executable, str(D / "collect.py")])
    print("복구 성공" if ok() else "복구 실패 — 사람이 봐야 한다")

이걸 몇 시간 뒤에 한 번 더 돌게 걸어두세요. 아침 8시 30분에 수집, 11시에 확인. 그러면 아침에 인터넷이 잠깐 끊겼어도 점심 전에 스스로 메꿉니다.

여기가 진짜 갈림길입니다

대부분의 자동화는 4단계가 없습니다. 그래서 두 달쯤 뒤에 조용히 멈춰 있고, 본인은 여전히 돌고 있다고 믿습니다. 감지만 하는 층은 만들지 마세요. 알림을 보내는 대신 다시 돌리는 게 낫습니다. 알림은 못 보는 날이 반드시 옵니다.


STEP 5

성공을 무엇으로 판정할지 정한다

이 단계가 이 문서에서 제일 중요합니다. 30분 중에 5분을 여기 쓰세요.

대부분 이렇게 판정합니다 — 실행했더니 에러가 안 났으니 성공. 그런데 이 판정은 스크립트가 아무 일도 안 하고 끝났을 때도 통과합니다. 수집처 목록이 비어 있어도, 응답이 빈 값이어도 에러는 안 납니다.

성공은 상태로 판정해야 합니다. "이 일이 제대로 끝났다면 세상에 무엇이 남아 있어야 하는가"를 먼저 정하고, 그게 있는지를 봅니다. 위 check.py가 파일의 존재만이 아니라 크기까지 본 이유가 이것입니다.

판정 기준을 정하는 법
나쁜 판정                        좋은 판정
─────────────────────────────────────────────────────────
에러 문구가 없다              →  오늘 날짜 파일이 있고 비어 있지 않다
스크립트가 끝났다             →  줄 수가 최소 N개 이상이다
로그에 성공이라고 찍혔다      →  결과물을 다시 읽어서 형식이 맞다
내 화면에서 보인다            →  로그인하지 않은 상태에서도 보인다

마지막 줄은 발행·업로드류를 자동화할 때 꼭 필요합니다. 내 세션으로는 임시저장이나 비공개 상태도 보이거든요. 남의 눈에 보이는 것만 발행입니다.


DONE

여기까지가 첫 자동화입니다

다섯 개가 다 체크되면 이제 하나가 혼자 도는 상태입니다. 상태 지도로 치면 1에서 10으로 넘어온 거예요. 시간이 남기 시작하는 자리입니다.