Stern Grove 로또 응모를 다시 잊지 마세요!
요약
GitHub Actions와 Playwright를 활용하여 Stern Grove 음악 축제 로또 응모를 자동화하는 프로젝트 사례를 소개합니다. 코딩 에이전트와 Claude Code를 사용하여 브라우저 자동화 스크립트를 구축하고, 전체 개발 과정을 기록하는 방법을 다룹니다.
핵심 포인트
- GitHub Actions의 cron 스케줄을 이용한 정기적 작업 자동화
- Playwright를 활용한 웹 스크레이핑 및 브라우저 자동화 구현
- 코딩 에이전트를 활용한 효율적인 개발 프로세스 및 기록 관리
- JSON 파일을 이용한 간단한 상태 유지(State persistence) 전략
샌프란시스코의 여름이 왔다는 것은, 매주 제가 무료 Stern Grove Music Festival 로또 응모를 잊어버린다는 것을 의미합니다.
해결책은 무엇일까요? 저는 단순히 캘린더 알림을 설정하는 대신, 합리적인 개발자라면 할 법한 일을 했습니다. GitHub Actions를 통해 매주 실행되고, Playwright로 축제 웹사이트를 스크레이핑(Scraping)하며, 저를 대신해 로또에 자동으로 응모하는 Python 스크립트를 구축했습니다.
재미있는 점은 제가 이것을 어떻게 만들었느냐 하는 것입니다. 저는 코딩 에이전트(Coding Agent)에게 제가 원하는 바를 설명했고(저는 테니스 코트 예약 자동화, 데이터 시각화 등을 위해 브라우저 자동화(Browser Automation)를 많이 해봤습니다), Entire가 그 과정에서의 모든 프롬프트(Prompt), 도구 호출(Tool Call), 출력을 기록했기 때문에, 전체 과정이 어떻게 구성되었는지에 대한 완전하고 감사 가능한(Auditable) 기록을 가지고 있습니다. 만약 빌드 과정을 직접 다시 따라가 보고 싶다면, 여기 라이브 세션을 확인하세요. 그 과정을 안내해 드리겠습니다:
메시지 하나로 시작되었습니다
이 전체(entire) (ㅎㅎ) 프로젝트는 코딩 에이전트에게 보낸 단 하나의 메시지로 시작되었습니다(저는 Claude Code를 사용했지만, 어떤 에이전트든 상관없습니다!). 저는 에이전트에게 게임의 규칙을 알려주었습니다:
Stern Grove 로또는 각 공연 6주 전 오전 10시에 시작되어 응모를 위해 일주일 동안 열려 있습니다...
또한 저는 응모 버튼의 정확한 HTML을 전달했습니다. Stern Grove는 Tixologi를 통해 티켓팅을 운영하므로, 에이전트는 자신이 무엇을 다루고 있는지 알아야 했습니다.
그다음 저는 제 GitHub Action이 접근할 수 있는 비밀 정보들에 대해 말해주었습니다:
-
이름 (First name)
-
도시 (City)
-
이메일 (Email)
-
Resend API 키 (알림용)
-
lottery.py— GitHub Actions의 cron 스케줄에 의해 트리거되는 엔트리 포인트 (entry point) -
browser.py— 웹 스크레이핑 (web scraping) 및 브라우저 자동화 (browser automation)를 위한 모든 Playwright 로직 -
state.py— 중복 응모를 방지하기 위해entered_lotteries.json을 로드하고 저장 -
notify.py— Resend을 통한 성공 및 실패 이메일 알림
상태 유지 (State persistence)가 핵심적인 부분이었습니다. 일주일에 한 번 실행되는 작업을 위해 데이터베이스를 구축하는 대신, 응모된 로또 정보는 JSON 파일에 기록되며 각 실행이 끝난 후 리포지토리(repo)에 다시 커밋 (committed back to the repo) 됩니다.
{
"entered": [
{ "event_id": "abc123", "show": "Show Name", "entered_at": "2025-06-21T10:02:00Z" }
...
cron 스케줄은 워크플로 (workflow) 파일에 정의되어 있습니다:
# .github/workflows/lottery.yml
on:
schedule:
...
자동화 전 검사 (Inspecting before automating)
여기서 강조할 만한 단계가 있습니다. 브라우저 로직을 건드리기 전에, 에이전트는 추측하는 대신 실제 데이터 구조를 파악하기 위해 Stern Grove 페이지의 실제 요소인 window.tixologyWidget.concerts를 실시간 검사 (live inspection) 했습니다.
Tixology는 티켓 및 이벤트 관리 플랫폼입니다. 해당 검사를 통해 정확한 필드 이름, ISO 8601 날짜 형식, 그리고 이벤트 ID 구조를 문서화할 수 있었습니다. 캡처한 하나의 console log 덕분에 나중에 디버깅(debugging) 시간을 크게 줄일 수 있었습니다:
// 페이지가 실제로 노출하는 데이터
window.tixologyWidget.concerts
// → [{ eventId, name, startDate: "2025-07-13T19:00:00Z", ... }]
가정 대신 실제로 관찰된 데이터에 구현의 근거를 두는 것은, 첫 실전 실행에서 바로 작동하는 코드와 프로덕션 (production) 환경에서 조용히 실패하는 코드를 가르는 주요 차이점입니다.
상태를 리포지토리에 다시 푸시하기 (Pushing state back to the repo)
state.py는 단순히 JSON을 읽기만 하는 것이 아닙니다. 응모에 성공하면 GitHub 개인 액세스 토큰 (Personal Access Token, PAT)을 사용하여 업데이트 사항을 커밋(commit)하고 푸시(push)합니다. 이를 통해 다음 실행 시 이미 완료된 작업이 무엇인지 알 수 있습니다.
def commit_state(token: str) -> None:
subprocess.run(["git", "add", "entered_lotteries.json"], check=True)
subprocess.run(["git", "commit", "-m", "Update entered lotteries"], check=True)
...
리뷰 루프(review loop)가 실제 버그를 잡아냈습니다
에이전트(agent)가 notify.py를 추가했을 때, 리뷰어 서브 에이전트(reviewer sub-agent)가 즉시 디프(diff)에서 두 가지 문제를 지적했습니다:
logging모듈 대신print()문을 사용하고 있었습니다.- Resend API 키(이메일용) 초기화 코드가 중복되었습니다. 에이전트는 다음 작업으로 넘어가기 전에 이 두 가지를 모두 수정했습니다. 이것이 바로 작업 → 리뷰 루프(task → review loop)가 수행해야 할 역할입니다. 즉, 보통 몇 주 후에나 드러날 법한 부주의함을 즉각 잡아내는 것입니다.
import logging
logger = logging.getLogger(__name__)
...
모든 프롬프트(prompt), 도구 호출(tool call), 출력을 추적하는 방법
이 부분은 제가 정말 유용하다고 느끼는 지점입니다. Entire가 모든 것을 캡처했기 때문에, 자동화가 어떻게 구축되었는지 따로 기억할 필요가 없었습니다. Entire에서의 **세션(session)**은 AI 코딩 상호작용의 완전한 기록, 즉 모든 프롬프트(prompt), 응답(response), 도구 호출(tool call), 그리고 파일 변경 사항을 의미합니다. 위의 세션 링크에서 제 기록을 살펴볼 수 있습니다.
탐색 방법은 다음과 같습니다:
- 세션 찾기 (Find the session). 세션은 체크포인트 상세 페이지의 Sessions 탭에 존재합니다. 각 행에는 실행된 에이전트(저의 경우 Claude Code), 단계 수(step count), 시작 프롬프트(opening prompt), 그리고 타임스탬프(timestamp)가 표시됩니다. 하나를 클릭하면 타임라인을 열 수 있습니다.
- 타임라인 읽기 (Read the timeline). 세션 타임라인은 대화 내용을 순서대로 보여줍니다 — 저의 프롬프트, 에이전트의 응답, 확장 가능한 도구 호출 (tool calls), 그리고 런타임 태그 (runtime tags)가 포함됩니다. 메타데이터 패널은 해당 실행의 모델 (model), 소요 시간 (duration), 그리고 토큰 (token) 사용량을 요약하여 보여줍니다.
- 도구 호출 확장 (Expand any tool call). 도구 호출은 인라인 행으로 렌더링되며, 이를 확장하여 정확한 인자 (arguments)와 결과 (results)를 확인할 수 있습니다. 파일 수정 (file edits)의 경우, 확장을 통해 그 자리에서 바로 디프 (diff)를 볼 수 있습니다.
- 노이즈 필터링 (Filter the noise). 우측의 필터 레일 (filters rail)을 통해 프롬프트, 응답, 중간 단계 (intermediate steps), 체크포인트, 그리고 도구 호출 (file edits, bash, read 및 기타로 더 세분화됨) 중 무엇을 표시할지 전환할 수 있습니다. 저는 더 높은 수준의 개요만 보고 싶을 때, 더 깔끔한 뷰를 얻기 위해 이 항목들을 체크 해제했습니다.
- 하위 에이전트 상세 분석 (Drill into sub-agents). 에이전트가 Claude Code의
Task도구를 통해 리뷰어 하위 에이전트 (reviewer sub-agent)를 실행했을 때, Entire는 이를 자체적인 트랜스크립트 (transcript)와 도구 호출을 가진 별도의 세션으로 캡처하여 부모 체크포인트의 합계에 포함시켰습니다. 바로 그 지점에서print()/로깅 문제를 찾아낸 리뷰 루프 (review loop)를 확인할 수 있습니다. - 코드로 이동 (Jump to the code). 모든 세션은
Entire-Checkpoint트레일러 (trailer)를 통해 Git 커밋 (commits)으로 다시 연결되므로, 일치하는 체크포인트나 GitHub의 기반 커밋을 열 수 있습니다. 메타데이터 자체는 별도의entire/checkpoints/v1브랜치에 저장되어 메인 히스토리를 깔끔하게 유지합니다. 따라서 여러분이 보고 있는 스크린캐스트 (screencast)가 유일한 기록이 아닙니다 — 모든 라인 뒤에 숨겨진 전체 추론 과정이 해당 세션에 담겨 있으며, 언제든 되감아 볼 준비가 되어 있습니다.
처음에는 작동하지 않았습니다!
솔직히 말씀드리면: 첫 번째 실제 실행은 실패했습니다. CI는 첫 시도에 협조하는 경우가 드물며, 이번에도 예외는 아니었습니다. 두 가지 환경 문제 (environment issues)가 저를 괴롭혔습니다:
1. Ubuntu 버전 드리프트 (version drift). Playwright 호환성을 위해 러너(runner)를 Ubuntu 22.04로 고정해야 했습니다. ubuntu-latest가 2025년에 조용히 Ubuntu 24.04로 변경되었고, 그 과정에서 libasound2 패키지의 이름이 변경되었습니다.
2. Chromium 의존성 (dependencies). Playwright의 번들 설치기(bundled installer)에 의존하는 대신, Ubuntu 24.04의 올바른 패키지 이름을 사용하여 Chromium 의존성을 수동으로 설치하게 되었습니다.
수정 전은 다음과 같았습니다:
# 수정 전
- run: playwright install --with-deps chromium
명시적인 apt 설치와 더 가벼워진 Playwright 단계로 변경되었습니다:
# 수정 후
- run: |
sudo apt-get update
...
성공!
마침내 깔끔하게 실행되었습니다. Stern Grove와 Resend로부터 직접 확인 이메일을 받았습니다.
기술 스택(stack)이 멋지게 결합되었습니다:
- 브라우저 자동화 (browser automation)를 위한 Playwright
- 주간 cron 스케줄링 및 비밀값 관리 (secret management)를 위한 GitHub Actions
- 성공/실패 알림을 위한 Resend
- 이를 구축하고 실제 버그를 잡아내는 리뷰 루프를 수행한 코딩 에이전트 (coding agent) (Claude Code)
- 모든 프롬프트(prompt), 도구 호출(tool call), 출력을 캡처하여 커밋(commit)과 연결하는 Entire
여러분이 동일한 부품들로 무엇을 만들어낼지 정말 기대됩니다. 이제 여러분(그리고 저도) 이번 여름 Stern Grove 로또 응모를 잊지 않을 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기