이번 글에서는 복권 구매까지 진행을 해보겠습니다. 다음글에서는 마지막 코드 리펙토링을 진행하도록 하겠습니다.
이 글 끝까지 따라하면 구매 기능까지 구현하는데 진짜 구매하시려면 예치금을 충전하시면 가능합니다.
복권 구매는 단계를 나누어 보면 아래와 같습니다.
- 메인 페이지에서 복권 구매 버튼 클릭 (클릭 시 구매 팝업 열림)
- 복권 구매 번호 랜덤으로 발행(자동번호 발행 기능이 이미 있지만 코드 예제를 위해 수동으로 진행)
- 구매 버튼 클릭
element 조회, 클릭 기능으로 구현되지만 추가적으로 알아야할 것은 복권 구매 화면이 iframe 형식으로 구성이 되어있다는 것입니다. iframe 으로 되어있는 경우 selenium 을 통해 현재 frame 을 이동시켜줘야 원하는 element 등에 접근 가능합니다.
팝업 닫을 때 window 를 옮겨야했듯 frame 이 여러개인 경우 frame 또한 옮겨주어야 해당 frame 안에서의 조작이 가능합니다
우선 몇 개의 기능을 먼저 작성하겠습니다.
script 실행하기
def execute_script(script):
driver.execute_script(script)
윈도우 중에서 마지막에 열린 윈도우로 현재 윈도우 변경하기
def switch_to_last_opened_window():
driver.switch_to.window(driver.window_handles[-1])
현재 프레임을 iframe 으로 변경하기
def switch_to_iframe(iframe):
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.TAG_NAME, "iframe"))
)
driver.switch_to.frame(iframe)
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.TAG_NAME, "iframe"))
)
WebDriverWait 코드가 필요한 이유는 iframe 이 나중에 로드가 되어 약간의 딜레이를 주지 않고 바로 find~ 등을 진행할 경우 때때로 실패하는 것을 확인할 수 있습니다. 그렇기에 넉넉히 10초간 iframe tag 가 나오기를 기다리는 코드를 작성한 것입니다.
로또 번호 6개 발행하기
def pick_numbers():
picks = set([])
while 6 != len(picks):
n = random.randint(LOTTO_MIN_NUM, LOTTO_MAX_NUM)
picks.add(n)
return picks
화면 캡처하기
def save_screenshot():
driver.save_screenshot(f'{datetime.now().strftime("%Y%m%d_%H%M%S")}.png')
이제 기능 구현을 해보겠습니다.
1. 팝업 닫기
이전 글에서 진행했던 로그인 기능을 실행하면 로그인 완료 후 다시 팝업이 나타납니다. 그렇기에 로그인 완료 후에도 팝업 닫기 함수를 호출해야합니다.
sleep(1)
close_windows_if_not_main_url('common.do?method=main')
sleep(1) 을 추가한 이유는 팝업이 여러개 나타날 경우 팝업이 제대로 안 닫히는 경우가 있습니다. 그러므로 1초 동안 팝업이 다 열리기를 기다리는 것 입니다.
2. 복권 구매 버튼 클릭 시 발생되는 script 실행
execute_script('goLottoBuy(2);')
저는 개인적으로 가능하다면 script 을 실행하는 편입니다. 그 이유는 html 구조 변경보다 js 변경이 덜 빈번하기 때문입니다. 또한 element 가 화면에 보이지 않을 경우 find 나 click 을 하려하면 에러를 발생시키는데 script 실행은 그에 비해 유연하기 때문입니다. 물론 상황에 따라서 적합한게 다를 것 입니다.
3. 복권 구매 팝업 윈도우로 윈도우 이동
switch_to_last_opened_window()
4. 복권 번호 클릭을 위해 현재 frame 이동
switch_to_iframe(driver.find_elements(By.TAG_NAME, 'iframe')[0])
5. 복권 번호 6개 클릭 후 번호 선택 확인 버튼 클릭
for num in pick_numbers():
execute_script(f'$("#check645num{num}").prop("checked", true);')
click(driver.find_element(By.ID, 'btnSelectNum'))
각 숫자는 checkbox 의 형식으로 되어있습니다. id 의 규칙을 보면 check645num 뒤에 1~45가 붙어있는 것임을 알 수 있습니다. 그렇기에 랜덤으로 6개의 숫자를 단순히 check645num 뒤에 붙여 checked 속성을 true 로 부여하였습니다.
6. 구매하기 버튼 클릭
click(driver.find_element(By.ID, 'btnBuy'))
execute_script('closepopupLayerConfirm(true);')
구매하기 버튼을 클릭하면 한 번 더 묻는 컨펌 다이얼로그가 나옵니다. 여기서 저는 해당 확인 버튼을 클릭하기 보다 js script 을 실행시키도록 하였습니다.
위 코드까지 실행하면 아래와 같이 구매 완료 화면이 나옵니다.
7. 구매내역 확인 캡처
구매만 하고 끝낼 수 있지만 저 구매내역 확인을 캡처해서 저장하고 싶을 수 있습니다. 그렇기에 캡처하고 브라우저 종료하도록 하겠습니다.
sleep(2)
save_screenshot()
driver.quit()
전체 코드
from datetime import datetime
import random
from time import sleep
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from dotenv import load_dotenv
import os
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
load_dotenv()
LOTTO_MIN_NUM = 1
LOTTO_MAX_NUM = 45
def create_driver():
return webdriver.Chrome(service=Service('./driver/chromedriver_mac64_m1_97'))
driver = create_driver()
driver.get('https://dhlottery.co.kr/common.do?method=main')
def get_main_handle(main_url):
for window_handle in driver.window_handles:
driver.switch_to.window(window_handle)
print(f'현재 핸들러 name : {driver.current_window_handle}')
print(f'현재 url : {driver.current_url}')
print('')
if main_url in driver.current_url:
return window_handle
raise '메인 핸들러 찾지 못함'
def close_windows_if_not_main_url(main_url):
main_handle = get_main_handle(main_url)
for window_handle in driver.window_handles:
if main_handle != window_handle:
driver.switch_to.window(window_handle)
driver.close()
driver.switch_to.window(main_handle)
def send_keys(element, text):
element.send_keys(text)
def click(element):
element.click()
def execute_script(script):
driver.execute_script(script)
def switch_to_last_opened_window():
driver.switch_to.window(driver.window_handles[-1])
def switch_to_iframe(iframe):
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.TAG_NAME, "iframe"))
)
driver.switch_to.frame(iframe)
def pick_numbers():
picks = set([])
while 6 != len(picks):
n = random.randint(LOTTO_MIN_NUM, LOTTO_MAX_NUM)
picks.add(n)
return picks
def save_screenshot():
driver.save_screenshot(f'{datetime.now().strftime("%Y%m%d_%H%M%S")}.png')
close_windows_if_not_main_url('common.do?method=main')
click(driver.find_element(By.XPATH, '/html/body/div[1]/header/div[2]/div[2]/form/div/ul/li[1]/a'))
send_keys(driver.find_element(By.ID, 'userId'), os.environ.get('ID'))
send_keys(driver.find_element(By.NAME, 'password'), os.environ.get('PW'))
send_keys(driver.find_element(By.NAME, 'password'), Keys.ENTER)
sleep(1)
close_windows_if_not_main_url('common.do?method=main')
execute_script('goLottoBuy(2);')
sleep(1)
switch_to_last_opened_window()
switch_to_iframe(driver.find_elements(By.TAG_NAME, 'iframe')[0])
for num in pick_numbers():
execute_script(f'$("#check645num{num}").prop("checked", true);')
click(driver.find_element(By.ID, 'btnSelectNum'))
click(driver.find_element(By.ID, 'btnBuy'))
execute_script('closepopupLayerConfirm(true);')
sleep(2)
save_screenshot()
driver.quit()