diff --git a/.github/workflows/lotto-buy.yml b/.github/workflows/lotto-buy.yml index faf67e9..5d0b09f 100644 --- a/.github/workflows/lotto-buy.yml +++ b/.github/workflows/lotto-buy.yml @@ -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 diff --git a/lotto-runner/Dockerfile b/lotto-runner/Dockerfile new file mode 100644 index 0000000..ad01dde --- /dev/null +++ b/lotto-runner/Dockerfile @@ -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"] diff --git a/lotto-runner/docker-compose.yml b/lotto-runner/docker-compose.yml new file mode 100644 index 0000000..9de89c9 --- /dev/null +++ b/lotto-runner/docker-compose.yml @@ -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" diff --git a/lotto_auto_buy.py b/lotto-runner/lotto_auto_buy.py old mode 100755 new mode 100644 similarity index 75% rename from lotto_auto_buy.py rename to lotto-runner/lotto_auto_buy.py index 360a9f8..987be08 --- a/lotto_auto_buy.py +++ b/lotto-runner/lotto_auto_buy.py @@ -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() \ No newline at end of file +""" +동행복권(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() diff --git a/lotto-runner/requirements.txt b/lotto-runner/requirements.txt new file mode 100644 index 0000000..c6ffdf1 --- /dev/null +++ b/lotto-runner/requirements.txt @@ -0,0 +1,2 @@ +selenium +python-dotenv