🚚 refactor: lotto-runner 독립 배포 환경으로 분리

- lotto_auto_buy.py를 루트에서 lotto-runner/로 이동
- 로그 경로를 logs/ 하위로 변경 (볼륨 마운트 대응)
- Dockerfile: node:22 → python:3.11-bookworm, Chrome 직접 설치 추가
- docker-compose.yml 신규 생성 (NAS Container Manager용)
- CI: requirements.txt 및 스크립트 경로를 lotto-runner/ 기준으로 수정
- Slack 알림 전송 기능 구현 (send_slack 함수)
- Chrome 컨테이너 구동 안정화 플래그 추가
- popupLayerAlert JS 강제 숨김으로 클릭 인터셉트 해결
This commit is contained in:
hyeonggil
2026-03-15 22:09:28 +09:00
parent 6ea84c3a7e
commit 7738d1a74f
5 changed files with 449 additions and 339 deletions

View File

@@ -61,7 +61,7 @@ jobs:
python3 --version
python3 -m venv .venv
.venv/bin/python -m pip install --upgrade pip setuptools wheel
.venv/bin/pip install -r requirements.txt
.venv/bin/pip install -r lotto-runner/requirements.txt
- name: Chrome 설치
uses: browser-actions/setup-chrome@v1
@@ -76,4 +76,4 @@ jobs:
env:
LOTTO_USER_ID: ${{ secrets.LOTTO_USER_ID }}
LOTTO_USER_PW: ${{ secrets.LOTTO_USER_PW }}
run: .venv/bin/python3 lotto_auto_buy.py
run: .venv/bin/python3 lotto-runner/lotto_auto_buy.py

39
lotto-runner/Dockerfile Normal file
View File

@@ -0,0 +1,39 @@
FROM python:3.11-bookworm
ENV DEBIAN_FRONTEND=noninteractive
ENV VIRTUAL_ENV=/opt/venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Chrome 시스템 의존성 설치
RUN apt-get update && apt-get install -y \
fonts-liberation fonts-noto-cjk \
libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 \
libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 \
libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 \
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 \
libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 \
libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 \
libxss1 libxtst6 xdg-utils curl wget gnupg ca-certificates unzip \
&& rm -rf /var/lib/apt/lists/*
# Google Chrome 설치 (.deb 직접 다운로드 방식 - NAS 방화벽에 안정적)
RUN wget -q -O /tmp/google-chrome.deb \
https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \
&& apt-get update \
&& apt-get install -y /tmp/google-chrome.deb \
&& rm /tmp/google-chrome.deb \
&& rm -rf /var/lib/apt/lists/*
# Python 가상환경 및 패키지 설치
RUN python3 -m venv /opt/venv \
&& /opt/venv/bin/python -m pip install --upgrade pip setuptools wheel
WORKDIR /app
COPY requirements.txt /tmp/requirements.txt
RUN /opt/venv/bin/pip install -r /tmp/requirements.txt
COPY lotto_auto_buy.py /app/lotto_auto_buy.py
RUN mkdir -p /app/logs
CMD ["python", "lotto_auto_buy.py"]

View File

@@ -0,0 +1,16 @@
services:
lotto-runner:
build:
context: .
dockerfile: Dockerfile
image: lotto-runner:latest
shm_size: "256mb"
env_file:
- .env
environment:
- LOTTO_USER_ID
- LOTTO_USER_PW
- SLACK_WEBHOOK_URL
volumes:
- ./logs:/app/logs
restart: "no"

727
lotto_auto_buy.py → lotto-runner/lotto_auto_buy.py Executable file → Normal file
View File

@@ -1,337 +1,390 @@
"""
동행복권(dhlottery.co.kr) 로또 6/45 자동 구매 스크립트
- 매주 금요일 자동 실행 (cron/스케줄러 연동)
- 5게임 자동 구매
- Selenium 기반 자동화
사전 준비:
pip install selenium
Chrome 브라우저 + ChromeDriver 설치 필요
"""
import os
import sys
import time
import json
import logging
import urllib.request
from datetime import datetime
# .env 파일 자동 로드
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
# ─────────────────────────────────────────────
# 설정
# ─────────────────────────────────────────────
CONFIG = {
"USER_ID": os.environ.get("LOTTO_USER_ID", "hyeonggil2"),
"USER_PW": os.environ.get("LOTTO_USER_PW", "Rlaehdbs062$"),
"BUY_COUNT": 1, # 구매 게임 수 (1~10)
"HEADLESS": True, # True: 브라우저 창 숨김, False: 브라우저 창 표시
"LOG_FILE": "lotto_log.json",
"SLACK_WEBHOOK_URL": os.environ.get("SLACK_WEBHOOK_URL", ""),
}
# 로깅 설정
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.StreamHandler(),
logging.FileHandler("lotto_auto.log", encoding="utf-8"),
],
)
logger = logging.getLogger(__name__)
def create_driver():
"""Chrome WebDriver 생성"""
options = Options()
if CONFIG["HEADLESS"]:
options.add_argument("--headless=new")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-gpu")
options.add_argument("--window-size=1920,1080")
# 자동화 탐지 우회
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
driver = webdriver.Chrome(options=options)
driver.execute_cdp_cmd(
"Page.addScriptToEvaluateOnNewDocument",
{"source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"},
)
driver.implicitly_wait(5)
return driver
def login(driver):
"""동행복권 사이트 로그인"""
logger.info("로그인 시도 중...")
driver.get("https://www.dhlottery.co.kr/login")
time.sleep(2)
# 아이디/비밀번호 입력
wait = WebDriverWait(driver, 10)
id_input = wait.until(EC.element_to_be_clickable((By.ID, "inpUserId")))
id_input.click()
id_input.send_keys(CONFIG["USER_ID"])
pw_input = wait.until(EC.element_to_be_clickable((By.ID, "inpUserPswdEncn")))
pw_input.click()
pw_input.send_keys(CONFIG["USER_PW"])
# 로그인 버튼 클릭
login_btn = wait.until(EC.element_to_be_clickable((By.ID, "btnLogin")))
login_btn.click()
# /login이 URL에서 완전히 사라질 때까지 대기 (최대 15초)
try:
WebDriverWait(driver, 15).until(lambda d: "/login" not in d.current_url)
except:
pass
logger.info(f"로그인 후 URL: {driver.current_url}")
# 로그인 성공 확인 (URL이 /login에서 벗어나면 성공)
if "/login" in driver.current_url:
logger.error("로그인 실패! 아이디/비밀번호를 확인하세요.")
return False
logger.info("로그인 성공!")
return True
def check_balance(driver):
"""예치금 잔액 확인"""
driver.get("https://www.dhlottery.co.kr/mypage/home")
time.sleep(2)
try:
balance_elem = driver.find_element(By.CSS_SELECTOR, "#totalAmt")
balance_text = balance_elem.text.replace(",", "").replace("", "").strip()
balance = int(balance_text) if balance_text.isdigit() else 0
logger.info(f"현재 예치금 잔액: {balance:,}")
return balance
except Exception as e:
logger.warning(f"잔액 확인 실패: {e}")
return -1
def buy_lotto(driver, game_count=5):
"""
로또 6/45 자동 구매
game_count: 구매할 게임 (1~10)
"""
logger.info(f"로또 {game_count}게임 자동 구매 시작...")
# 로또 구매 페이지 이동
driver.get("https://ol.dhlottery.co.kr/olotto/game/game645.do")
time.sleep(3)
# iframe 전환 (로또 구매 페이지는 iframe 내부에 있음)
try:
# 팝업 닫기 (있을 경우)
try:
close_btns = driver.find_elements(By.CSS_SELECTOR, ".close, .btn_close")
for btn in close_btns:
try:
btn.click()
time.sleep(0.5)
except:
pass
except:
pass
wait = WebDriverWait(driver, 10)
# "자동" 선택
auto_btn = wait.until(
EC.element_to_be_clickable((By.ID, "num2"))
)
auto_btn.click()
time.sleep(1)
# 게임 수 설정 (드롭다운에서 선택)
from selenium.webdriver.support.ui import Select
try:
select_elem = driver.find_element(By.ID, "amoundApply")
select = Select(select_elem)
select.select_by_value(str(game_count))
time.sleep(0.5)
except Exception:
# 드롭다운이 없는 경우 자동번호 버튼을 game_count번 클릭
for i in range(game_count):
auto_select_btn = driver.find_element(By.ID, "btnSelectNum")
auto_select_btn.click()
time.sleep(0.5)
# "자동번호 선택" 버튼 클릭
try:
select_num_btn = driver.find_element(By.ID, "btnSelectNum")
select_num_btn.click()
time.sleep(1)
except:
pass
# "구매하기" 버튼 클릭
buy_btn = wait.until(
EC.element_to_be_clickable((By.ID, "btnBuy"))
)
buy_btn.click()
time.sleep(2)
# 구매 확인 팝업 "확인" 클릭
confirm_btn = wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "#popupLayerConfirm input[value='확인']"))
)
confirm_btn.click()
time.sleep(3)
logger.info("구매 요청 완료! 구매 결과를 확인합니다...")
# 구매 결과 확인
result = extract_purchase_result(driver)
return result
except Exception as e:
logger.error(f"구매 중 오류 발생: {e}")
return None
def extract_purchase_result(driver):
"""구매 결과(번호) 추출"""
result = {
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"games": [],
"status": "unknown",
}
try:
time.sleep(2)
# 회차 / 추첨일 추출
try:
result["round"] = driver.find_element(By.ID, "buyRound").text
result["draw_date"] = driver.find_element(By.ID, "drawDate").text
except:
pass
# 번호 추출: #reportRow li div.nums span
game_rows = driver.find_elements(By.CSS_SELECTOR, "#reportRow li")
for row in game_rows:
nums = row.find_elements(By.CSS_SELECTOR, "div.nums span")
numbers = [int(n.text) for n in nums if n.text.strip().isdigit()]
if numbers:
result["games"].append(numbers)
if result["games"]:
result["status"] = "success"
logger.info(f"구매 성공! {len(result['games'])}게임")
for i, game in enumerate(result["games"], 1):
logger.info(f" 게임 {i}: {game}")
else:
result["status"] = "completed_but_numbers_unknown"
logger.info("번호 추출 실패. 마이페이지에서 확인하세요.")
except Exception as e:
result["status"] = "error"
result["error"] = str(e)
logger.warning(f"결과 추출 중 오류: {e}")
return result
def save_log(result):
"""구매 기록 저장"""
log_file = CONFIG["LOG_FILE"]
history = []
if os.path.exists(log_file):
try:
with open(log_file, "r", encoding="utf-8") as f:
history = json.load(f)
except:
history = []
history.append(result)
with open(log_file, "w", encoding="utf-8") as f:
json.dump(history, f, ensure_ascii=False, indent=2)
logger.info(f"구매 기록이 {log_file}에 저장되었습니다.")
def main():
"""메인 실행 함수"""
logger.info("=" * 50)
logger.info("로또 6/45 자동 구매 스크립트 시작")
logger.info(f"실행 시간: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
logger.info("=" * 50)
# 아이디/비밀번호 확인
if CONFIG["USER_ID"] == "여기에_아이디_입력" or CONFIG["USER_PW"] == "여기에_비밀번호_입력":
logger.error("아이디/비밀번호가 설정되지 않았습니다!")
logger.error("환경변수(LOTTO_USER_ID, LOTTO_USER_PW)를 설정하거나 스크립트 내 CONFIG를 수정하세요.")
sys.exit(1)
driver = None
try:
driver = create_driver()
# 1. 로그인
if not login(driver):
logger.error("로그인 실패로 종료합니다.")
sys.exit(1)
# 2. 잔액 확인
balance = check_balance(driver)
cost = CONFIG["BUY_COUNT"] * 1000
if balance >= 0 and balance < cost:
logger.error(f"예치금 부족! 현재: {balance:,}원, 필요: {cost:,}")
logger.error("동행복권 사이트에서 예치금을 충전해주세요.")
sys.exit(1)
# 3. 로또 구매
result = buy_lotto(driver, CONFIG["BUY_COUNT"])
# 4. 결과 저장
if result:
save_log(result)
else:
save_log({
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"status": "failed",
"error": "구매 결과를 확인할 수 없습니다.",
})
logger.info("스크립트 실행 완료!")
except Exception as e:
logger.error(f"예상치 못한 오류: {e}")
save_log({
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"status": "error",
"error": str(e),
})
finally:
if driver:
driver.quit()
logger.info("브라우저 종료")
if __name__ == "__main__":
main()
"""
동행복권(dhlottery.co.kr) 로또 6/45 자동 구매 스크립트
- 매주 금요일 자동 실행 (cron/스케줄러 연동)
- 5게임 자동 구매
- Selenium 기반 자동화
사전 준비:
pip install selenium
Chrome 브라우저 + ChromeDriver 설치 필요
"""
import os
import sys
import time
import json
import logging
import urllib.request
from datetime import datetime
# .env 파일 자동 로드
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
# ─────────────────────────────────────────────
# 설정
# ─────────────────────────────────────────────
CONFIG = {
"USER_ID": os.environ.get("LOTTO_USER_ID", "hyeonggil2"),
"USER_PW": os.environ.get("LOTTO_USER_PW", "Rlaehdbs062$"),
"BUY_COUNT": 1, # 구매 게임 수 (1~10)
"HEADLESS": True, # True: 브라우저 창 숨김, False: 브라우저 창 표시
"LOG_FILE": "logs/lotto_log.json",
"SLACK_WEBHOOK_URL": os.environ.get("SLACK_WEBHOOK_URL", ""),
}
# 로깅 설정
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.StreamHandler(),
logging.FileHandler("logs/lotto_auto.log", encoding="utf-8"),
],
)
logger = logging.getLogger(__name__)
def create_driver():
"""Chrome WebDriver 생성"""
options = Options()
if CONFIG["HEADLESS"]:
options.add_argument("--headless=new")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-gpu")
options.add_argument("--disable-software-rasterizer")
options.add_argument("--remote-debugging-port=9222")
options.add_argument("--user-data-dir=/tmp/chrome-user-data")
options.add_argument("--window-size=1920,1080")
# 자동화 탐지 우회
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
driver = webdriver.Chrome(options=options)
driver.execute_cdp_cmd(
"Page.addScriptToEvaluateOnNewDocument",
{"source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"},
)
driver.set_page_load_timeout(30) # 페이지 로드 최대 30초
driver.set_script_timeout(30) # JS 실행 최대 30초
driver.implicitly_wait(5)
return driver
def login(driver):
"""동행복권 사이트 로그인"""
from selenium.common.exceptions import TimeoutException
logger.info("로그인 시도 중...")
try:
driver.get("https://www.dhlottery.co.kr/login")
except TimeoutException:
logger.warning("로그인 페이지 로드 타임아웃 — 로드된 부분으로 계속 진행")
time.sleep(2)
# 아이디/비밀번호 입력
wait = WebDriverWait(driver, 10)
id_input = wait.until(EC.element_to_be_clickable((By.ID, "inpUserId")))
id_input.click()
id_input.send_keys(CONFIG["USER_ID"])
pw_input = wait.until(EC.element_to_be_clickable((By.ID, "inpUserPswdEncn")))
pw_input.click()
pw_input.send_keys(CONFIG["USER_PW"])
# 로그인 버튼 클릭
login_btn = wait.until(EC.element_to_be_clickable((By.ID, "btnLogin")))
login_btn.click()
# /login이 URL에서 완전히 사라질 때까지 대기 (최대 15초)
try:
WebDriverWait(driver, 15).until(lambda d: "/login" not in d.current_url)
except:
pass
logger.info(f"로그인 후 URL: {driver.current_url}")
# 로그인 성공 확인 (URL이 /login에서 벗어나면 성공)
if "/login" in driver.current_url:
logger.error("로그인 실패! 아이디/비밀번호를 확인하세요.")
return False
logger.info("로그인 성공!")
return True
def check_balance(driver):
"""예치금 잔액 확인"""
driver.get("https://www.dhlottery.co.kr/mypage/home")
time.sleep(2)
try:
balance_elem = driver.find_element(By.CSS_SELECTOR, "#totalAmt")
balance_text = balance_elem.text.replace(",", "").replace("", "").strip()
balance = int(balance_text) if balance_text.isdigit() else 0
logger.info(f"현재 예치금 잔액: {balance:,}")
return balance
except Exception as e:
logger.warning(f"잔액 확인 실패: {e}")
return -1
def buy_lotto(driver, game_count=5):
"""
로또 6/45 자동 구매
game_count: 구매할 게임 (1~10)
"""
logger.info(f"로또 {game_count}게임 자동 구매 시작...")
# 로또 구매 페이지 이동 (타임아웃 시 로드된 부분으로 계속 진행)
from selenium.common.exceptions import TimeoutException
try:
driver.get("https://ol.dhlottery.co.kr/olotto/game/game645.do")
except TimeoutException:
logger.warning("페이지 로드 타임아웃 — 로드된 부분으로 계속 진행")
time.sleep(2)
# iframe 전환 (로또 구매 페이지는 iframe 내부에 있음)
try:
wait = WebDriverWait(driver, 10)
# 모든 레이어 팝업을 JS로 직접 숨기기 (z-index 오버레이 완전 제거)
driver.execute_script("""
var popups = document.querySelectorAll(
'#popupLayerAlert, #popupLayerConfirm, .layer-alert, .layer-popup, .pop_wrap'
);
popups.forEach(function(el) { el.style.display = 'none'; });
""")
time.sleep(0.5)
logger.info("팝업 레이어 숨김 처리 완료")
# "자동" 탭 선택 — onclick 함수(selectWayTab)를 직접 호출
driver.execute_script("selectWayTab(1);")
time.sleep(1)
time.sleep(1)
# 게임 수 설정 (드롭다운에서 선택)
from selenium.webdriver.support.ui import Select
try:
select_elem = driver.find_element(By.ID, "amoundApply")
select = Select(select_elem)
select.select_by_value(str(game_count))
time.sleep(0.5)
except Exception:
# 드롭다운이 없는 경우 자동번호 버튼을 game_count번 클릭
for i in range(game_count):
auto_select_btn = driver.find_element(By.ID, "btnSelectNum")
auto_select_btn.click()
time.sleep(0.5)
# "자동번호 선택" 버튼 클릭
try:
select_num_btn = driver.find_element(By.ID, "btnSelectNum")
select_num_btn.click()
time.sleep(1)
except:
pass
# "구매하기" 버튼 클릭
buy_btn = wait.until(
EC.element_to_be_clickable((By.ID, "btnBuy"))
)
buy_btn.click()
time.sleep(2)
# 구매 확인 팝업 "확인" 클릭
confirm_btn = wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "#popupLayerConfirm input[value='확인']"))
)
confirm_btn.click()
time.sleep(3)
logger.info("구매 요청 완료! 구매 결과를 확인합니다...")
# 구매 결과 확인
result = extract_purchase_result(driver)
return result
except Exception as e:
logger.error(f"구매 중 오류 발생: {e}")
return None
def extract_purchase_result(driver):
"""구매 결과(번호) 추출"""
result = {
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"games": [],
"status": "unknown",
}
try:
time.sleep(2)
# 회차 / 추첨일 추출
try:
result["round"] = driver.find_element(By.ID, "buyRound").text
result["draw_date"] = driver.find_element(By.ID, "drawDate").text
except:
pass
# 번호 추출: #reportRow li div.nums span
game_rows = driver.find_elements(By.CSS_SELECTOR, "#reportRow li")
for row in game_rows:
nums = row.find_elements(By.CSS_SELECTOR, "div.nums span")
numbers = [int(n.text) for n in nums if n.text.strip().isdigit()]
if numbers:
result["games"].append(numbers)
if result["games"]:
result["status"] = "success"
logger.info(f"구매 성공! {len(result['games'])}게임")
for i, game in enumerate(result["games"], 1):
logger.info(f" 게임 {i}: {game}")
else:
result["status"] = "completed_but_numbers_unknown"
logger.info("번호 추출 실패. 마이페이지에서 확인하세요.")
except Exception as e:
result["status"] = "error"
result["error"] = str(e)
logger.warning(f"결과 추출 중 오류: {e}")
return result
def send_slack(result):
"""Slack Webhook으로 구매 결과 알림 전송"""
url = CONFIG["SLACK_WEBHOOK_URL"]
if not url:
return
status = result.get("status", "unknown")
games = result.get("games", [])
round_no = result.get("round", "")
draw_date = result.get("draw_date", "")
if status == "success" and games:
games_text = "\n".join(
f" 게임 {i}: {' '.join(str(n) for n in nums)}"
for i, nums in enumerate(games, 1)
)
text = (
f":ticket: *로또 구매 완료!*\n"
f"회차: {round_no} 추첨일: {draw_date}\n"
f"{games_text}"
)
elif status == "failed":
text = f":warning: *로또 구매 실패*\n{result.get('error', '')}"
else:
text = f":x: *로또 구매 오류*\n{result.get('error', status)}"
try:
payload = json.dumps({"text": text}).encode("utf-8")
req = urllib.request.Request(
url,
data=payload,
headers={"Content-Type": "application/json"},
)
urllib.request.urlopen(req, timeout=10)
logger.info("Slack 알림 전송 완료")
except Exception as e:
logger.warning(f"Slack 알림 전송 실패: {e}")
def save_log(result):
"""구매 기록 저장"""
log_file = CONFIG["LOG_FILE"]
history = []
if os.path.exists(log_file):
try:
with open(log_file, "r", encoding="utf-8") as f:
history = json.load(f)
except:
history = []
history.append(result)
with open(log_file, "w", encoding="utf-8") as f:
json.dump(history, f, ensure_ascii=False, indent=2)
logger.info(f"구매 기록이 {log_file}에 저장되었습니다.")
def main():
"""메인 실행 함수"""
logger.info("=" * 50)
logger.info("로또 6/45 자동 구매 스크립트 시작")
logger.info(f"실행 시간: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
logger.info("=" * 50)
# 아이디/비밀번호 확인
if CONFIG["USER_ID"] == "여기에_아이디_입력" or CONFIG["USER_PW"] == "여기에_비밀번호_입력":
logger.error("아이디/비밀번호가 설정되지 않았습니다!")
logger.error("환경변수(LOTTO_USER_ID, LOTTO_USER_PW)를 설정하거나 스크립트 내 CONFIG를 수정하세요.")
sys.exit(1)
driver = None
try:
driver = create_driver()
# 1. 로그인
if not login(driver):
logger.error("로그인 실패로 종료합니다.")
sys.exit(1)
# 2. 잔액 확인
balance = check_balance(driver)
cost = CONFIG["BUY_COUNT"] * 1000
if balance >= 0 and balance < cost:
logger.error(f"예치금 부족! 현재: {balance:,}원, 필요: {cost:,}")
logger.error("동행복권 사이트에서 예치금을 충전해주세요.")
sys.exit(1)
# 3. 로또 구매
result = buy_lotto(driver, CONFIG["BUY_COUNT"])
# 4. 결과 저장 및 Slack 알림
if result:
save_log(result)
send_slack(result)
else:
failed = {
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"status": "failed",
"error": "구매 결과를 확인할 수 없습니다.",
}
save_log(failed)
send_slack(failed)
logger.info("스크립트 실행 완료!")
except Exception as e:
logger.error(f"예상치 못한 오류: {e}")
error_result = {
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"status": "error",
"error": str(e),
}
save_log(error_result)
send_slack(error_result)
finally:
if driver:
driver.quit()
logger.info("브라우저 종료")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,2 @@
selenium
python-dotenv