Files
my-lotto/lotto_auto_buy.py
hyeonggil 149b97b0ca
Some checks failed
ci / ci (22, ubuntu-latest) (push) Failing after 2m48s
feat: .env 파일 로드 및 Slack 웹훅 알림 지원 추가
2026-03-15 15:14:09 +09:00

337 lines
11 KiB
Python
Executable 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": "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()