[Python] 파이썬으로 SRT 예매 프로그램 만들기 (1) 기능 구현하기

안녕하세요?

 

과거에 처음 코딩을 배우던 시절 짠 허접한 SRT 매진 표 예매 게시물을 올렸었는데요, 이번에 코드를 새로 짜는 김에 파이썬 초보분들에게 도움이 될 수 있도록 어떤 식으로 접근하였는지 처음부터 설명하고자 합니다.

 

이 게시물에서는 파이썬을 이용하여 SRT 표 검색 및 자동 예매, 매진 표 예매를 위한 자동 새로고침에 대한 코드를 다룰 것입니다. 이후 게시물에서는 기차 조건 선택이나 예약 대기, 텔레그램 알람 등의 추가 기능을 구현하고, 더 완성된 프로그램의 형태로 만드는 과정을 공유하고자 합니다.

 

 

<본 게시물 완성 코드의 작동 동영상>

SRT 매진 표 자동 예매 시도

 

     

    1. 개요

    1-1. 목표

    과거 처음 파이썬을 배우던 시절에 만들었던 부끄럽고 비효율적인 코드를 새롭게 작성하며, 파이썬을 통한 기차 예매 자동화나 업무 자동화에 관심이 있는 분들께 도움이 되는 참고용 게시물을 적고자 합니다. 웹크롤링이나 기차 예매나 방법은 완전히 동일하며, 이것을 응용하여 취미로 파이썬을 배우는 분들의 현업에서도 도움이 되었으면 합니다.

     

    이 게시물에서는 기본적인 기능 구현을 위한 이해를 돕기 위하여 단순한 순서로 코드를 작성 및 설명하였으며, 추가 게시물에서 다시 형식을 갖춘 프로그램을 짜서 공유할 수 있도록 하겠습니다. 본 게시물의 완성 코드만으로도 기차 예매에는 문제가 없습니다.

     

    참고로 전체 코드는 3-2-3. 항목4-1. 항목에 있습니다.

     

    1-2. 준비물

     1-2-1. Chromedriver 

    웹브라우저를 조작하기 위하여 크롬드라이버가 필요합니다. 구글에서 검색하거나 아래 게시물을 참조하여 다운받습니다.

    Chromedriver 다운로드 및 설치하는 법(https://kminito.tistory.com/78)

     

    Chromedriver 다운로드 및 설치하는 법

    웹드라이버는 프로그래밍 언어를 이용하여 웹브라우저를 직접적으로 조작할 수 있도록 해 주는 툴입니다. 여기서는 Chrome의 Webdriver인 Chromedriver를 다운 받는 법을 알아봅니다. 1. Chrome 설치하기 (

    kminito.tistory.com

     

     1-2-2. Selenium 설치하기

    크롬드라이버를 조작하기 위하여 파이썬 Selenium 패키지가 필요합니다.

    커맨드 창에 아래와 같은 명령어를 입력하여 설치합니다. 

    pip install selenium

     

     

     

    참고 사항 : 연습은 Jupyter Notebook으로 하시는 게 편합니다. 인터넷 창 다시 시작할 필요 없이 한번 실행한 인터넷 브라우저를 계속 조작 가능합니다.

     

    2. SRT 홈페이지 로그인하기

     2-1. SRT 화면 홈페이지 들어가기

     

    <코드1>

    # -*- coding: utf-8 -*-
    from selenium import webdriver
    
    driver = webdriver.Chrome("chromedriver") # Webdriver 파일의 경로를 입력
    driver.get('https://etk.srail.co.kr/cmc/01/selectLoginForm.do') # 이동을 원하는 페이지 주소 입력

    실행 화면

     

    2-2. 로그인하기

     2-2-1. 아이디 비밀번호 입력하기

    입력이나 클릭을 하고 싶은 부분을 특정하기 위하여 검사 도구가 필요합니다.  크롬에서 "Ctrl+Shift+C"를 누르면 검사 도구 기능을 이용할 수 있습니다. 혹은 F12를 눌러서 뜨는 개발자 도구 화면에서 왼쪽 상단 커서 모양을 클릭해도 됩니다.

     

     

    이제 회원번호 입력 칸의 코드를 확인합니다. 검사 도구를 실행하고, 회원번호 입력칸을 클릭하면 오른쪽에 해당 소스가 자동으로 표시되는 것을 알 수 있습니다. 여기서는 해당 input field의 id가 srchDvNm01 라는 점에 주목합니다. 전체 코드를 검색해보면 id가 srchDvNm01인 요소는 회원번호 입력 칸 밖에 없습니다.

     

    이것을 이용하여 회원번호 칸에 숫자를 집어넣을 수 있습니다.

     

     

    같은 방법으로 확인해보면 비밀번호 입력 칸의 input tag 아이디는 hmpgPwdCphd01 입니다.

     

     

    > 정리하자면 id가 srchDvNm01hmpgPwdCphd01 인 요소를 찾으면 그 곳이 회원번호와 비밀번호를 입력하는 곳입니다. id를 가지고 요소를 찾기 위하여 Selenium의 By 모듈을 Import 해 줍니다.

     

    상단에 추가할 코드

    from selenium.webdriver.common.by import By

     

    <코드2>

    # -*- coding: utf-8 -*-
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    
    driver = webdriver.Chrome("chromedriver")
    driver.get('https://etk.srail.co.kr/cmc/01/selectLoginForm.do') # 로그인 화면으로 이동
    driver.implicitly_wait(15) # 페이지 다 뜰 때 까지 기다림
    
    driver.find_element(By.ID, 'srchDvNm01').send_keys('12345677234') # 회원번호
    driver.find_element(By.ID, 'hmpgPwdCphd01').send_keys("1111111111") # 비밀번호

     직관적인 코드이므로 별도 설명이 필요 없을 것으로 예상됩니다. 

     

     

    <실행 결과>

     

    2-2-2. 로그인 버튼 누르기

     

    검사 도구(Ctrl+Shift+C)를 이용하여 확인 버튼의 소스를 확인합니다. 위에서 처럼 고유한 id가 없으므로, XPath를 이용합니다. 해당 버튼에 해당하는 소스코드에 마우스 우클릭하여 Copy -> Copy full Xpath를 클릭합니다.

     

     

    복사된 XPath : /html/body/div[1]/div[4]/div/div[2]/form/fieldset/div[1]/div[1]/div[2]/div/div[2]/input

     

    복사된 XPath를 이용하여 셀레늄에서 해당 버튼을 클릭합니다.

    위의 코드에 아래의 내용을 추가하면, 회원번호와 비밀번호를 입력하고 확인 버튼을 누르게 됩니다.

     

    <코드3>

    driver.find_element(By.XPATH, '//*[@id="login-form"]/fieldset/div[1]/div[1]/div[2]/div/div[2]/input').click()
    driver.implicitly_wait(5)

    * 여기서는 By.ID가 아닌 By.XPATH를 이용했습니다.

     

     

     

    3. 기차 예매하기

    3-1. 기차 조회하기

    3-1-1. 기차 예매 페이지로 이동

     

    먼저 로그인 후 상단 메뉴의 일반 승차권 조회 페이지로 이동합니다. 해당 페이지의 주소는 https://etk.srail.kr/hpg/hra/01/selectScheduleList.do 이므로, 페이지 이동을 위하여 아래와 같은 코드를 추가합니다.

    # 기차 조회 페이지로 이동
    driver.get('https://etk.srail.kr/hpg/hra/01/selectScheduleList.do')
    driver.implicitly_wait(5)

     

    3-1-2. 검색 조건 - 출발지, 도착지 입력하기

     

     

    로그인과 마찬가지로 소스를 확인해보면 출발지와 도착지 입력칸 모두 고유 id가 있습니다.

     

     - 출발지 id : dptRsStnCdNm

     - 도착지 id : arvRsStnCdNm

     

     

    id를 이용하여 해당 요소를 찾고, default 값을 지운 뒤, 원하는 값을 입력합니다.

    # 출발지 입력
    dep_stn = driver.find_element(By.ID, 'dptRsStnCdNm')
    dep_stn.clear() 
    dep_stn.send_keys("동탄")
    
    # 도착지 입력
    arr_stn = driver.find_element(By.ID, 'arvRsStnCdNm')
    arr_stn.clear()
    arr_stn.send_keys("수서")

     

    3-1-3. 출발 날짜  입력하기

     

    여기서는 다른 입력칸과 달리 값을 직접적으로 입력하는 것이 불가하고, 드롭다운 리스트에서 선택을 해야 합니다. 소스를 확인해보면 겉으로 보이는 드롭다운 리스트는 실제 입력칸이 아니라 그냥 깔끔하게 꾸미기 위해서 리스트를 만들어 놓은 것이고, 실제 드롭다운 리스트는 보이지 않도록 숨겨져 있습니다. 

     

     

    소스코드에 있는 "display: none;" 을 더블클릭하여 "display: true;"로 바꾸면 아래와 같이 숨겨져 있는 드롭다운 리스트가 화면에 표시됩니다. 해당 작업은 셀레늄에서 execute_script 메서드를 이용하면 됩니다. 

     

     

    # 날짜 드롭다운 리스트 보이게
    elm_dptDt = driver.find_element(By.ID, "dptDt")
    driver.execute_script("arguments[0].setAttribute('style','display: True;')", elm_dptDt)

     

    이제 날짜를 선택해야 하는데, SRT 승차권 예약조회는 1개월 이내 열차만 가능하여 선택지가 정해져 있습니다.

    소스코드의 option에 나와있는 value를 파이썬을 이용하여 직접 선택할 수 있습니다.

     

    선택 기능을 이용하기 위하여 Select 모듈을 임포트합니다.

    from selenium.webdriver.support.select import Select
    
    # 2021년 10월 01일 기차 선택
    Select(driver.find_element(By.ID,"dptDt")).select_by_value("20211001")

     

    3-1-4. 출발 시간 입력하기

    출발 시간 입력도 출발 날짜와 완전히 동일한 방식입니다. 다만 여기서는 값을 선택할 때 옵션 Value가 아닌 Visible Text 를 선택하는 것이 더 직관적입니다. 다시 말해서, 아래의 옵션 리스트를 보면 02시 출발은 value가 02000으로 되어있는데, value가 아닌 실제 시간인 02를 기준으로 선택하는 것입니다. 

    # 출발 시간
    elm_dptTm = driver.find_element(By.ID, "dptTm")
    driver.execute_script("arguments[0].setAttribute('style','display: True;')", elm_dptTm)
    Select(driver.find_element(By.ID, "dptTm")).select_by_visible_text("12")

     마지막 줄을 보시면, 날짜 선택에서는 select_by_value로 썼으나 시간 선택에서는 select_by_visible_text 을 썼습니다.

     

     

    3-1-5 조회하기 버튼 누르기

    조회하기 버튼의 소스를 보시면 value가 조회하기인 input tag임을 알 수 있습니다. 여기서는 input tag의 value를 이용한 xpath로 해당 요소를 찾아 클릭합니다.

    driver.find_element(By.XPATH,"//input[@value='조회하기']").click()

     -> find_element에서 tag의 value를 사용하는 방법은 해당 페이지의 소스가 변경되어 원하는 버튼의 위치나 순서가 바뀌었을 때에도 변하지 않으므로 유용합니다. 원하는 요소를 찾는 방법은 여러가지가 있지만 그때그때에 적절한 방법을 사용하시면 됩니다.

     

    3-1-6 <여기까지 전체 코드>

     - 동탄에서 수서로 가는 2021년 10월 01일 12시 이후 기차 검색

     - 회원번호와 비밀번호는 수정 필요

     - 모듈 임포트는 코드의 맨 위로 이동했음

    # -*- coding: utf-8 -*-
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.select import Select
    
    
    driver = webdriver.Chrome("chromedriver")
    driver.get('https://etk.srail.co.kr/cmc/01/selectLoginForm.do')
    driver.implicitly_wait(15)
    
    # 로그인
    driver.find_element(By.ID, 'srchDvNm01').send_keys('12345677234') # 회원번호
    driver.find_element(By.ID, 'hmpgPwdCphd01').send_keys("1111111111") # 비밀번호
    driver.find_element(By.XPATH, '//*[@id="login-form"]/fieldset/div[1]/div[1]/div[2]/div/div[2]/input').click()
    driver.implicitly_wait(5)
    
    # 기차 조회 페이지로 이동
    driver.get('https://etk.srail.kr/hpg/hra/01/selectScheduleList.do')
    driver.implicitly_wait(5)
    
    # 출발지 입력
    dep_stn = driver.find_element(By.ID, 'dptRsStnCdNm')
    dep_stn.clear()
    dep_stn.send_keys("동탄")
    
    # 도착지 입력
    arr_stn = driver.find_element(By.ID, 'arvRsStnCdNm')
    arr_stn.clear()
    arr_stn.send_keys("수서")
    
    # 출발 날짜
    elm_dptDt = driver.find_element(By.ID, "dptDt")
    driver.execute_script("arguments[0].setAttribute('style','display: True;')", elm_dptDt)
    Select(driver.find_element(By.ID,"dptDt")).select_by_value("20211001")
    
    # 출발 시간
    elm_dptTm = driver.find_element(By.ID, "dptTm")
    driver.execute_script("arguments[0].setAttribute('style','display: True;')", elm_dptTm)
    Select(driver.find_element(By.ID, "dptTm")).select_by_visible_text("12")
    
    # 조회하기 버튼 클릭
    driver.find_element(By.XPATH,"//input[@value='조회하기']").click()
    driver.implicitly_wait(5)

     

    <실행 결과>

    검색 결과 화면까지 정상적으로 이동이 됩니다.

     

     

     

     

    3-2. 기차 예매하기

     여기서 해야 할 것은 검색 결과에서 나온 예약하기 버튼을 누르는 것입니다만, 나중에 매진 표를 찾거나 특실, 일반실을 구분하여 예약하기 위하여 결과 테이블이 어떤 식으로 구성되어 있는지 조금 살펴보도록 하겠습니다. 

     

    3-2-1. 결과 화면 구성 살펴보기

    <행>

     

    기차 조회 결과 리스트의 한 줄 한 줄은 <tr> 태그로 구성되어 있습니다. 패턴을 찾기 위해 각 행의 CSS Selector를 확인해보면,,

     

    - 1번째 행 : #result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr:nth-child(1)

    - 2번째 행 : #result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr:nth-child(2)

    - 3번째 행 : #result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr:nth-child(3)

    - 4번째 행 : .... 

     

     

    모두 "#result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr"를 공통으로 가지고 있으므로, find_elements 메소드를 이용하여 모든 tr 요소를 파이썬 리스트 형태로 가져올 수 있습니다.

    train_list = driver.find_elements(By.CSS_SELECTOR, '#result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr')
    
    print(len(train_list)) # 결과: 10

    총 10행으로 이루어져 있으므로, 결과 리스트의 길이를 확인하면 10이 나옵니다.

     

     

    <열>

    각 행의 칸들은 <td> 태그로 구성되어 있습니다. 패턴을 찾기 위해 첫째 행의 각 칸의 CSS Selector를 확인해보면,,

     

     - 1번째 칸 :#result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr:nth-child(1) > td:nth-child(1)

     - 2번째 칸 : #result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr:nth-child(1) > td.trnGp

     - 3번째 칸 : #result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr:nth-child(1) > td.trnNo

     - 4번째 칸 : #result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr:nth-child(1) > td:nth-child(4)

     - 5번째 칸 : ...

     

     

    2, 3번째 칸은 td:nth-child의 형식이 아니라 class 이름인 trnGp와 trnNo로 가 들어가있지만 td:nth-child(2)td:nth-child(3)로 변경하여도 문제가 없습니다. 다만 나중에 해당 테이블의 앞쪽에 열이 추가 되면, 칸의 순서가 바뀌므로 코드가 작동하지 않을 수 있습니다.

     

     

    <행과 열>

    위에서 알아낸 패턴을 이용하여 루프를 돌려서 해당 표의 텍스트 값을 뽑아보겠습니다.

    for i in range(1, len(train_list)+1):
        for j in range(3, 8):
            text = driver.find_element(By.CSS_SELECTOR, f"#result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr:nth-child({i}) > td:nth-child({j})").text.replace("\n"," ")
            print(text, end="")
        print()
      
    # 실행 결과
    # 329수서 12:10동탄 12:25예약하기 좌석선택예약하기 좌석선택
    # 333수서 12:50동탄 13:06예약하기 좌석선택예약하기 좌석선택
    # 337수서 13:30동탄 13:45예약하기 좌석선택예약하기 좌석선택
    # 611수서 14:10동탄 14:25예약하기 좌석선택예약하기 좌석선택
    # 345수서 15:30동탄 15:45예약하기 좌석선택예약하기 좌석선택
    # 347수서 15:55동탄 16:11예약하기 좌석선택예약하기 좌석선택
    # 661수서 16:11동탄 16:27예약하기 좌석선택예약하기 좌석선택
    # 353수서 17:00동탄 17:15예약하기 좌석선택예약하기 좌석선택
    # 663수서 17:10동탄 17:25예약하기 좌석선택예약하기 좌석선택
    # 615수서 18:05동탄 18:20예약하기 좌석선택예약하기 좌석선택

     

     

    3-2-2. 기차 예매하기 (1)

     

    원하는 조건을 걸어서 기차를 예매할 수 있습니다. 여기서는 검색 결과에서 나오는 상위 두개 기차에 일반실 좌석이 있을 경우 예약 버튼을 누르고, 매진이면 다시 조회하도록 코드를 짜겠습니다.

     

    <코드> - 조회 결과 중 상위 두 개의 기차의 일반실에 "예약하기" 버튼이 활성화 되어 있는 경우를 확인

    for i in range(1, 3):
        # td:nth-child(7)이 일반실 칸
        standard_seat = driver.find_element(By.CSS_SELECTOR, f"#result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr:nth-child({i}) > td:nth-child(7)").text
        
        if "예약하기" in standard_seat:
            print("예약 가능")

     

     

    첫 열차의 예약하기 CSS Selector: #result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr:nth-child(1) > td:nth-child(7) > a

     

    <코드> '예약하기'가 활성화 되어있을 경우 예약 버튼 클릭하기

     

    for i in range(1, 3):
        standard_seat = driver.find_element(By.CSS_SELECTOR, f"#result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr:nth-child({i}) > td:nth-child(7)").text
        
        if "예약하기" in standard_seat:
            print("예약 가능")        
            driver.find_element(By.XPATH, f"/html/body/div[1]/div[4]/div/div[3]/div[1]/form/fieldset/div[6]/table/tbody/tr[{i}]/td[7]/a/span").click()
            
            # CSS Selector 사용시 예약하기 대신 좌석선택이 눌러지는 문제가 있어 XPATH로 변경
            # driver.find_element(By.CSS_SELECTOR, f"#result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr:nth-child({i}) > td:nth-child(7) > a").click()

     

    <실행 결과>

    표가 있을 경우에는 예매가 됩니다.

     

     

     

     

    3-2-2. 기차 예매하기 (2) 매진일 경우 다시 조회하기

    아래와 같이 목표하는 표가 모두 매진일 경우 다시 조회 버튼을 누르도록 하겠습니다.

     

    여기에서는 다시 조회버튼을 누르는 것이 셀레늄에서 바로 click()을 날리는 것이 잘 작동하지 않을 때가 있어, 아래와 같은 방식으로 자바스크립트를 이용하여 버튼을 클릭하도록 하겠습니다. 

    submit = driver.find_element(By.XPATH, "//input[@value='조회하기']")
    driver.execute_script("arguments[0].click();", submit)

     

     

    매진일 경우 5초 간격으로 새로고침을 하는 코드는 아래와 같습니다.

    <코드 - 매진일 경우 새로고침 하는 부분>

    import time
    
    reserved = False
    
    while True:
        for i in range(1, 5): # 상위 4개 기차 확인
            standard_seat = driver.find_element(By.CSS_SELECTOR, f"#result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr:nth-child({i}) > td:nth-child(7)").text
    
            if "예약하기" in standard_seat:
                print("예약 가능")       
                driver.find_element(By.XPATH, f"/html/body/div[1]/div[4]/div/div[3]/div[1]/form/fieldset/div[6]/table/tbody/tr[{i}]/td[7]/a/span").click()
                reserved = True
                break
    
        if not reserved:
            # 5초 기다리기
            time.sleep(5)
            
            # 다시 조회하기
            submit = driver.find_element(By.XPATH, "//input[@value='조회하기']")
            driver.execute_script("arguments[0].click();", submit)
            print("새로고침")
    
            driver.implicitly_wait(10)
            time.sleep(1)
        else:
            break

    reserved라는 변수를 이용하여, 

     - 예약이 성공하면 reserved를 True로 변경 -> 루프 종료

     - 예약이 성공하지 않으면 while 루프를 반복하였습니다.

     

    3-2-3. 전체 코드 

     - 출발역 : 울산(동도사)

     - 도착역 : 동탄

     - 출발 날짜 : 2021년 9월 26일

     - 출발 시간 : 14시 이후

     - 검색 결과 상위 4개 기차 중 예약 가능한 기차 예약

    # -*- coding: utf-8 -*-
    import time
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.select import Select
    from selenium.webdriver.common.action_chains import ActionChains
    
    
    driver = webdriver.Chrome("chromedriver")
    driver.get('https://etk.srail.co.kr/cmc/01/selectLoginForm.do')
    driver.implicitly_wait(15)
    
    # 로그인
    driver.find_element(By.ID, 'srchDvNm01').send_keys('1111111111') # 회원번호
    driver.find_element(By.ID, 'hmpgPwdCphd01').send_keys("1111111111") # 비밀번호
    driver.find_element(By.XPATH, '//*[@id="login-form"]/fieldset/div[1]/div[1]/div[2]/div/div[2]/input').click()
    driver.implicitly_wait(5)
    
    # 기차 조회 페이지로 이동
    driver.get('https://etk.srail.kr/hpg/hra/01/selectScheduleList.do')
    driver.implicitly_wait(5)
    
    # 출발지 입력
    dep_stn = driver.find_element(By.ID, 'dptRsStnCdNm')
    dep_stn.clear()
    dep_stn.send_keys("울산(통도사)")
    
    # 도착지 입력
    arr_stn = driver.find_element(By.ID, 'arvRsStnCdNm')
    arr_stn.clear()
    arr_stn.send_keys("동탄")
    
    # 출발 날짜
    elm_dptDt = driver.find_element(By.ID, "dptDt")
    driver.execute_script("arguments[0].setAttribute('style','display: True;')", elm_dptDt)
    Select(driver.find_element(By.ID,"dptDt")).select_by_value("20210926")
    
    # 출발 시간
    elm_dptTm = driver.find_element(By.ID, "dptTm")
    driver.execute_script("arguments[0].setAttribute('style','display: True;')", elm_dptTm)
    Select(driver.find_element(By.ID, "dptTm")).select_by_visible_text("14")
    
    # 조회하기 버튼 클릭
    driver.find_element(By.XPATH, "//input[@value='조회하기']").click()
    driver.implicitly_wait(5)
    
    # 
    reserved = False
    
    while True:
        for i in range(1, 5):
            standard_seat = driver.find_element(By.CSS_SELECTOR, f"#result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr:nth-child({i}) > td:nth-child(7)").text
    
            if "예약하기" in standard_seat:
                print("예약 가능")          
                driver.find_element(By.XPATH, f"/html/body/div[1]/div[4]/div/div[3]/div[1]/form/fieldset/div[6]/table/tbody/tr[{i}]/td[7]/a/span").click()
                reserved = True
                break
    
        if not reserved:
            # 5초 기다리기
            time.sleep(5)
            
            # 다시 조회하기
            submit = driver.find_element(By.XPATH, "//input[@value='조회하기']")
            driver.execute_script("arguments[0].click();", submit)
            print("새로고침")
    
            driver.implicitly_wait(10)
            time.sleep(1)
        else:
            break

     

     

    4. 코드 보완하기

    로그인 정보와 기차 검색 조건을 변수로 지정하여 간단히 변경할 수 있도록 합니다.

     

     

    4-1. <완성 코드>

    코드 상단에 지정한 변수를 알맞게 수정하여 기차 예매에 이용할 수 있습니다.

    # -*- coding: utf-8 -*-
    import time
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.select import Select
    
    
    member_num = "1234567890" # 회원번호
    pwd = "1111111111" # 비밀번호
    depart_station = "동탄" # 출발역 
    arrival_station = "수서" # 도착역
    depart_date = "20210926" # 출발 날짜 YYYYMMDD 형식
    depart_time = "14" # 출발 시간 00, 02, 04, ... ,20, 22 형식 
    number_of_trains = 4 # 검색 결과 상단에서부터 예약 가능 여부 확인할 기차 수 
    
    
    # 입력한 기차역은 아래 리스트에 있어야 함.
    station_list = ["수서", "동탄", "평택지제", "천안아산", "오송", "대전", "김천(구미)", "동대구",
                    "신경주", "울산(통도사)", "부산", "공주", "익산", "정읍", "광주송정", "나주", "목포"]
    
    
    driver = webdriver.Chrome("chromedriver")
    driver.get('https://etk.srail.co.kr/cmc/01/selectLoginForm.do')
    driver.implicitly_wait(15)
    
    # 로그인
    driver.find_element(By.ID, 'srchDvNm01').send_keys(member_num) # 회원번호
    driver.find_element(By.ID, 'hmpgPwdCphd01').send_keys(pwd) # 비밀번호
    driver.find_element(By.XPATH, '//*[@id="login-form"]/fieldset/div[1]/div[1]/div[2]/div/div[2]/input').click()
    driver.implicitly_wait(5)
    
    # 기차 조회 페이지로 이동
    driver.get('https://etk.srail.kr/hpg/hra/01/selectScheduleList.do')
    driver.implicitly_wait(5)
    
    # 출발지 입력
    dep_stn = driver.find_element(By.ID, 'dptRsStnCdNm')
    dep_stn.clear()
    dep_stn.send_keys(depart_station)
    
    # 도착지 입력
    arr_stn = driver.find_element(By.ID, 'arvRsStnCdNm')
    arr_stn.clear()
    arr_stn.send_keys(arrival_station)
    
    # 출발 날짜
    elm_dptDt = driver.find_element(By.ID, "dptDt")
    driver.execute_script("arguments[0].setAttribute('style','display: True;')", elm_dptDt)
    Select(driver.find_element(By.ID,"dptDt")).select_by_value(depart_date)
    
    # 출발 시간
    elm_dptTm = driver.find_element(By.ID, "dptTm")
    driver.execute_script("arguments[0].setAttribute('style','display: True;')", elm_dptTm)
    Select(driver.find_element(By.ID, "dptTm")).select_by_visible_text(depart_time)
    
    # 조회하기 버튼 클릭
    driver.find_element(By.XPATH, "//input[@value='조회하기']").click()
    driver.implicitly_wait(5)
    
    reserved = False
    
    while True:
        for i in range(1, number_of_trains+1):
            standard_seat = driver.find_element(By.CSS_SELECTOR, f"#result-form > fieldset > div.tbl_wrap.th_thead > table > tbody > tr:nth-child({i}) > td:nth-child(7)").text
    
            if "예약하기" in standard_seat:
                print("예약 가능")          
                driver.find_element(By.XPATH, f"/html/body/div[1]/div[4]/div/div[3]/div[1]/form/fieldset/div[6]/table/tbody/tr[{i}]/td[7]/a/span").click()
                reserved = True
                break
    
        if not reserved:
            # 5초 기다리기
            time.sleep(5)     
            # 다시 조회하기
            submit = driver.find_element(By.XPATH, "//input[@value='조회하기']")
            driver.execute_script("arguments[0].click();", submit)
            print("새로고침")
            driver.implicitly_wait(10)
            time.sleep(1)
        else:
            break

     

     

     

    4-2. 이후의 과제

    4-2-1 문제점

     - 예약하기가 아닌 좌석선택이 눌러지는 경우가 종종 발생함

     - 예약 버튼을 눌렀으나 실제 잔여석이 없는 경우에 대한 처리

    4-2-2 다음 게시물에서 다룰 것

     - 4-2-1의 문제점 해결하기

     - 코드 구조 변경하기 (함수 및 클래스 사용)

     - 특실/일반실 선택 구현하기

     - 예약대기 클릭 구현하기

     - 텔레그램으로 알람 보내기

     - CLI/GUI 프로그램으로 만들기

     - 각종 예외 처리

     - 기타 최적화

     

     

    5. 끝

     

     

     

     

     

    2편도 만들었습니다. (2022년 1월 15일)

     -> 예약 대기 설정 가능

     -> 잔여석없음 뜰 경우 다시 검색 가능

    https://kminito.tistory.com/82

     

    [Python] 파이썬으로 SRT 예매 프로그램 만들기 (2) 기능 업데이트

    안녕하세요? 2편이 많이 늦었네요. 1편에 이어 일부 기능을 업데이트하고 조금 더 완성도 있는 프로그램으로 만들고자 합니다. 1편 링크 https://kminito.tistory.com/79 [Python] 파이썬으로 SRT 예매 프로그

    kminito.tistory.com

     

    댓글

    Designed by JB FACTORY