commit ddcf244546cf1fad10b43f0ac1c67a1435f18fb2 Author: hyeonggil <> Date: Sun Mar 15 13:42:53 2026 +0900 feat: 로또 자동 구매 스크립트 및 GitHub Actions 설정 diff --git a/.github/workflows/lotto-buy.yml b/.github/workflows/lotto-buy.yml new file mode 100644 index 0000000..a4c81a9 --- /dev/null +++ b/.github/workflows/lotto-buy.yml @@ -0,0 +1,30 @@ +name: 로또 자동 구매 + +on: + schedule: + - cron: '0 0 * * 5' # 매주 금요일 00:00 UTC = 09:00 KST + workflow_dispatch: # 수동 실행 버튼 + +jobs: + buy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Python 설정 + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: 의존성 설치 + run: pip install -r requirements.txt + + - name: Chrome 설치 + uses: browser-actions/setup-chrome@v1 + + - name: 로또 구매 실행 + env: + LOTTO_USER_ID: ${{ secrets.LOTTO_USER_ID }} + LOTTO_USER_PW: ${{ secrets.LOTTO_USER_PW }} + run: python lotto_auto_buy.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..513433c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +lotto_log.json +lotto_auto.log +launchd.log +__pycache__/ +*.pyc +.DS_Store diff --git a/lotto_auto_buy.py b/lotto_auto_buy.py new file mode 100755 index 0000000..51e9335 --- /dev/null +++ b/lotto_auto_buy.py @@ -0,0 +1,328 @@ +""" +동행복권(dhlottery.co.kr) 로또 6/45 자동 구매 스크립트 +- 매주 금요일 자동 실행 (cron/스케줄러 연동) +- 5게임 자동 구매 +- Selenium 기반 웹 자동화 + +사전 준비: + pip install selenium + Chrome 브라우저 + ChromeDriver 설치 필요 +""" + +import os +import sys +import time +import json +import logging +from datetime import datetime +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", +} + +# 로깅 설정 +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 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7cb6656 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +selenium