파이썬/셀레늄크롤링

[python-파이썬] SRT 표(티켓) 자동 예매하기

모두의 실험실 2022. 6. 11. 21:25
728x90



 

 

안녕하세요 모두의 파이썬입니다. 크롤링에 대해 지금까지 약 20개 작성을 하였습니다. 그런데 지금까지는 작성한 글을 보면 사이트에 존재하는 정보들에 대해 크롤링을 하였습니다. 로그인을 하거나 입력값을 필요로 하지 않는 종류의 크롤링이었습니다. 하지만 파이썬을 알고, 크롤링 구현 실력이 증가하면서 자동화를 하고 싶은 영역이 확장되어 실생활에 적용할 사례들이 폭발적으로 증가할 것이라 생각됩니다. 그래서 국내 가장 빠른 기차인 SRT 표를 자동 예매하는 방법에 대해 알아보겠습니다

전체 글을 요약하면, SRT 로그인에서부터 결제 직전인 예매까지 구현한다

우선 SRT 홈페이지로 접속한다. 예매를 해봤다면, 한 번쯤은 홈페이지에 들어와 봤을 것이다. 다음과 같은 화면을 볼 수 있다. 예매를 하기 위해서는 로그인이 필수이다. 로그인 버튼을 눌러서 로그인 절차를 거친다.

ⓒ SRT 메인화면 , 출처 SRT


다음과 같은 화면을 볼 수 있다. 로그인이 주목적이므로 코드에서는 메인화면은 스킵하고, 로그인 화면부터 접속할 예정이다.

ⓒ SRT 로그인화면 , 출처 SRT


자동 예매를 진행하기 위해 자동화 브라우저가 우선 필요하다. 자동화 브라우저 사용을 위해 pip install selenium으로 selenium 패키지 설치와 Chrome 드라이버 다운로드는 구글 검색을 통해 선행하길 권장한다. 설치가 완료되었다면, 크롬 자동화 브라우저를 활성화하고, SRT 로그인 화면으로 접속해 본다. 크롬 드라이버는 d:에 저장을 한다. SRT 로그인 주소는 https://etk.srail.kr/cmc/01/selectLoginForm.do 이며, driver.get(login_url)을 수행하여, SRT로그인 화면으로 접속을 완료한다.

 

 

 

 

 

 

In [1]:

##자동화 크롬 브라우저를 위한 필수 패키지
from selenium import webdriver

##크롬드라이버 경로
path = "D:\\chromedriver.exe"
##브라우저 활성화
driver = webdriver.Chrome(path)
##SRT 로그인 url
login_url = 'https://etk.srail.kr/cmc/01/selectLoginForm.do'
##SRT 로그인페이지로 연결
driver.get(login_url)
 
 
이 페이지에서 구현하여야 할 것은 3가지이다, 1회원번호 입력, 2비밀 번호 입력, 3확인 클릭을 구현하려면 개발자 모드(F12 클릭)에서 원하는 UI에 해당하는 html을 확인한다. 1-3방식 모두 html을 찾는 방법는 유사하다.  1회원번호입력을 구현한다. html을 찾는 방법은 마우스 커서를 움직이고, 메인화면에 파란색 투명도로 음영이 상세한 위치까지 갈 때까지 ▶기호를 클릭하면서 html의 하부구조를 계속하여 찾아나가야 한다.

 

ⓒ 자동크롬 브라우저로 접속한 SRT 로그인화면 , 출처 SRT


회원번호 입력란에 정확히 음영이 처리될 때까지 html을 구조를 스캔했고, 해당 id가 "srchDvNm01" 임을 확인 완료하였다. html에 id가 존재한다면, id를 활용할 수 있는 코드로 접근한다.

ⓒ 자동크롬 브라우저로 접속한 SRT 로그인화면 , 출처 SRT


custom_id에 회원번호 Element를 정의한 다음 clear()로 빈칸으로 초기화한다. 그리고 send_keys를 통해 회원번호 입력을 실행한다. 회원번호 '12345678'이 이상 없이 입력된 것을 확인할 수 있다.

ⓒ 회원번호를 자동으로 입력한 SRT 로그인화면 , 출처 SRT

 

 

 

 

 

 

 

In [6]:

## html id활용 용이하기 위해 선언
from selenium.webdriver.common.by import By

## 회원번호
custom_id_num = '12345678'
## 회원번호 입력을 위한 Element
custom_id = driver.find_element(By.ID, 'srchDvNm01')
## 빈칸으로 초기화
custom_id.clear()
## send_keys를 통해 회원번호 입력
custom_id.send_keys(custom_id_num)


비밀번호 입력 구현은 회원번호 구현 방식과 유사하며 id는 'hmpgPwdCphd01'이다. 코드 구현한 결과는 다음과 같다.

ⓒ 회원번호, 비밀번호를 자동으로 입력한 SRT 로그인화면 , 출처 SRT

 

 

 

 

 

 

 

In [7]:

## 비밀번호
passwd_num = '87654321'
## 비밀번호 입력을 위한 Element
passwd = driver.find_element(By.ID, 'hmpgPwdCphd01')
## 빈칸으로 초기화
passwd.clear()
## send_keys를 통해 비밀번호 입력
passwd.send_keys(passwd_num)

 

3확인 클릭을 구현하는 방법도 앞 1-2과 유사하다. 확인 버튼에 음영이 나타날 때까지 html을 찾는다 찾았지만, id가 존재하지 않는다. 그래서 xpath 방식으로 구현한다

 

ⓒ 확인버튼 구현하는 과정의 SRT 로그인화면 , 출처 SRT

 

 

 

 

 

 

 

In [8]:

## 확인버튼 xpath
btn_add = '/html/body/div/div[4]/div/div[2]/form/fieldset/div[1]/div[1]/div[2]/div/div[2]/input'
## 확인버튼 클릭을 위한 Element
login_btn = driver.find_element_by_xpath(btn_add)
## 확인버튼 클릭
login_btn.click()
 
 
로그인을 완료하면, 상단에 회원 이름과 SRT 초기 화면으로 접속된다. 그다음은 승차권 예약으로 접속을 한다. 페이지를 변경시키는 것은 driver.get('이동할 사이트 url')을 사용한다.

 

ⓒ 회원번호, 비밀번호를 자동으로 입력한 SRT 로그인화면 , 출처 SRT


일반승차권, 단체 승차권, 할인 승차권 세 가지가 있지만, 가장 많이 사용하는 일반승차권 예매에 대해서만 확인한다. 자동화하려는 항목이 여러 가지이지만, 크게 2가지로 나눠본다. 첫째는 출발지, 도착지, 출발 일자, 출발 일시이다. 둘째는 인원 정보와 차종 구분이다.

ⓒ 로그인후 일반승차권 조회 화면 , 출처 SRT


출발지, 도착지, 출발 일자, 출발 일시 모두 id가 존재하기에 driver.find_element(By.ID, 'id 이름') 사용하여 구한다. 출발지와 도착지의 경우에는 지역명을 입력해야 하며, 입력하기 전에는 초기화하기 위해서 clear() 함수를 사용한다. 그리고 지역명을 다시 한번 살펴 보면 다음과 같다. station_list = ['수서','동탄','평택지제','천안아산','오송', '대전','김천(구미)','서대구','동대구','신경주', '울산(통도사)','부산','공주','익산','정읍','광주송정','나주','목포'] 출발지와 도착지를 정확하게 입력해야 한다. 출발 일자와 출발시간은 리스트 형태의 값을 선택해야 하며, 22년 6월 13일 경우 '20220613'의 문자 형태로 입력해야 한다. 출발 일자의 경우 금일보다 미래여야 하며, 한 달 이내만 선택할 수 있다. 날짜를 한 달 이후 또는 과거의 날짜를 입력할 경우, 당일로 설정되게 예외처리를 하였다. 출발 시간의 경우 00시에서 22시까지 2시간 단위이며, 0시의 경우 00 + 0000 그리고 10시의 경우 '100000'을 입력해야 한다. 잘못 입력할 시에는 이전 값으로 설정하게 코드를 구현하였다.

 

 

 

 

 

 

In [13]:

##승차권 예약으로 접속
driver.get('https://etk.srail.kr/hpg/hra/01/selectScheduleList.do?pageId=TK0101010000')

## 지역명 예시 
station_list = ['수서','동탄','평택지제','천안아산','오송',
                '대전','김천(구미)','서대구','동대구','신경주',
                '울산(통도사)','부산','공주','익산','정읍','광주송정','나주','목포']

##출발지 선택
##출발지 정보
departure_areaName = "동탄"
##출발지 입력을 위한 Element
departure_area = driver.find_element(By.ID, 'dptRsStnCdNm')
##사전에 입력된 내용 지우기
departure_area.clear() 
##출발지 입력
departure_area.send_keys(departure_areaName)  

##도착지 선택
##도착지 정보
arrival_areaName = "수서"
##도착지 입력을 위한 Element
arrival_area = driver.find_element(By.ID, 'arvRsStnCdNm')
##사전에 입력된 내용 지우기
arrival_area.clear() 
##도착지 입력
arrival_area.send_keys(arrival_areaName)

##리스트를 포함하는 Element를 다루기 위한 package선언
from selenium.webdriver.support.select import Select
##출발일자 선택
##출발일자
target_date = 20220613
##출발지 입력을 위한 Element
departure_date = driver.find_element(By.ID, 'dptDt')

##숫자로 입력 없거나 에러이면 오늘로 진행 
try: 
    ##일자 리스트 중에서 출발일자 선택
    Select(departure_date).select_by_value(str(target_date))
except:
    ##오늘년월일 정보 입력
    today = datetime.now().strftime('%Y%m%d')
    ##일자 리스트 중에서 오늘을 선택
    Select(departure_date).select_by_value(str(today))
    
##출발시간 선택
##출발시간
target_time = 18 

## 문자열 구성을 위해 10 미만은 문자 0을 앞에 추가
if target_time < 10:
    target_time = '0'+ str(target_time)
else:
    target_time = str(target_time) 

##출발시간 입력을 위한 Element
departure_time = driver.find_element(By.ID, 'dptTm')

    
try: 
    ## 타겟 시간 선택
    Select(departure_time).select_by_value(target_time+ '0000')
except:
    pass

인원 정보도 출발 일자와 유사하게 리스트 형태의 Element 형태로 코드 구현해야 하며, 어른과 어린이 그리고 경로만 다뤄본다. 중증 및 경증장애인은 여러분이 쉽게 해결할 수 있으리라 판단된다. 어른, 어린이 그리고 경로 탑승객 수 구현 방식은 모두 동일하다. 어른을 살펴보면, id가 없기 때문에 xpath를 사용하여 구현해야 한다. 그리고 리스트 형태이기 Select 클래스로 탑승객 수를 입력한다. 0-9명까지 입력 가능하며, 범위를 벗어나는 인원일 경우 0명으로 선택한다. 다음은 차종 구분이다. SRT 표 예매만 구현한다. 차종 구분은 id 형태이며, id는 'trnGpCd300'이다. click()으로 차종 선택을 완료한다. 그리고 이 페이지에서 마지막인 조회 버튼도 id 형태이므로 id 'search_top_tag'로 element를 구현하고, click()으로 일반승차권 예매를 완료한다.

ⓒ 일반승차권 조회화면 , 출처 SRT

 

 

 

 

 

 

In [20]:
##어른 탑승객 수 
adult_num = 3
##어른탑승객수 Element xpath (id가 아닌 xpath형태로 코드구현)
adult_select_add = '/html/body/div[1]/div[4]/div/div[2]/form/fieldset/div[1]/div/ul/li[2]/div[2]/div[1]/select'
##어른 탑승객 수를 위한 Element
adult_passenger = driver.find_element_by_xpath(adult_select_add)
##어른 탑승객 수 선택
try:
    Select(adult_passenger).select_by_value(str(adult_num))
except:
    ## 에러가 나면 탑승객수 0명으로 선택
    Select(adult_passenger).select_by_value(str(0)) 

##어린이 탑승객 수
child_num = 0
##어린이 탑승객 수 Element xpath (id가 아닌 xpath형태로 코드구현)
child_select_add = '/html/body/div[1]/div[4]/div/div[2]/form/fieldset/div[1]/div/ul/li[2]/div[2]/div[2]/select'
##어린이 탑승객 수를 위한 Element 
child_passenger = driver.find_element_by_xpath(child_select_add)
##어린이 탑승객 수 선택
try:
    Select(child_passenger).select_by_value(str(child_num))
except:
    ## 에러가 나면 탑승객수 0명으로 선택
    Select(child_passenger).select_by_value(str(0))
    
##경로탑승색 수
senior_num = 0
##경로 탑승객 수 Element xpath (id가 아닌 xpath형태로 코드구현)
senior_select_add = '/html/body/div[1]/div[4]/div/div[2]/form/fieldset/div[1]/div/ul/li[2]/div[2]/div[3]/select'
##경로 탑승객 수를 위한 Element 
senior_passenger = driver.find_element_by_xpath(senior_select_add)
##경로 탑승객 수 선택
try:
    Select(senior_passenger).select_by_value(str(senior_num))
except:
    ## 에러가 나면 탑승객수 0명으로 선택
    Select(senior_passenger).select_by_value(str(0)) 
    
##STR차량선택
srt_select_radio_btn = driver.find_element(By.ID, 'trnGpCd300')
srt_select_radio_btn.click()

##조회버튼클릭
serch_btn = driver.find_element(By.ID, 'search_top_tag')
serch_btn.click()
 
 
확인 버튼을 클릭 후 차량 스케줄표로 이동하게 되는데, 순간적인 지연이 있기 때문에, 예외 처리까지 고민해야 한다. 자동으로 예약을 해야 하는데, 코드 실행이 멈추면 안 된다. 그래서 페이지를 이동할 때마다, 페이지 로딩 안정화를 위해, 확인하는 코드를 만든다. 스케줄에 상관없이 운행시간 '보기'는 항상 존재한다. 그래서 반복문으로 존재하는지 확인하여 try 문을 통과하면, 반복문을 break 하여, 예매하기 클릭 단계로 넘어가게 한다.

ⓒ 차량스케쥴 및 예약하기 화면 , 출처 SRT

특실과 일반실 중에서 특실 경우만 다뤄보고 사용자의 선택에 따라 일반실만 다룰 수도 있고, 우선순위를 부여하여 특실이 없다면, 일반실 또는 일반실이 없다면, 특실 예약하기 등 사용자의 의도에 맞게 알고리즘을 설계할 수 있다. 구현된 알고리즘은 지정된 일자/시간의 가장 빠른 스케줄 예매이며, 특실 예매이다. 그래서 화면과 같이 예약하기 버튼이 활성화되어 있어서 반복문 없이도 바로 예약하고, break 문으로 진입할 수 있다. 예약하기 버튼은 xpath로 구현하였다. click()으로 예약하기를 완료하면, 코로나 관련 팝업창이 활성화된다. ok 버튼을 클릭하면 예약하기가 완료된다.

ⓒ 예약완료후 코로나관련 팝업창 , 출처 SRT

 

In [38]:
##조회버튼클릭
serch_btn = driver.find_element(By.ID, 'search_top_tag')
serch_btn.click()

##sleep을 위해서 time package 선언
import time
##운행시간 확인으로 페이지 안정화 이후 예매선택
train_schedule_add = '/html/body/div/div[4]/div/div[3]/div[1]/form/fieldset/div[6]/table/tbody/tr[1]/td[10]/a'
for i in range(10):    
    try:
        driver.find_element_by_xpath(train_schedule_add)
        print('pass')
        break
    except:
        print('delay count: ', i)
    time.sleep(0.05)
    
##예약이 될때까지 반복문수행
while True:    
    try:
        ##특실예약
        vip_seat_btn_add = '/html/body/div/div[4]/div/div[3]/div[1]/form/fieldset/div[6]/table/tbody/tr[1]/td[6]/a'
        vip_seat_btn = driver.find_element_by_xpath(vip_seat_btn_add)
        vip_seat_btn.click()

        ##일반예약
        #normal_seat = '/html/body/div/div[4]/div/div[3]/div[1]/form/fieldset/div[6]/table/tbody/tr[1]/td[7]/a'
        #normal_seat_btn = driver.find_element_by_xpath(normal_seat_btn_add)
        #normal_seat_btn.click()
    except:
        print('try book again ......')
        pass
    
    ##예약이 완료되면, 코로나 관련 안내문이 활성화
    try:
        popup_add = '/html/body/div[2]/div[3]/div/button'
        popup = driver.find_element_by_xpath(popup_add)
        popup.click()  
        break
    except:
        print('popup is not activate')
        pass
    time.sleep(3)
    driver.refresh()
print('book finished')
 
 
 
예약을 완료하면 예약 정보를 상세히 볼 수 있다
ⓒ 예약완료후 예약정보 확인하는 창 , 출처 SRT
 
 
로그인부터 시작해서 승차권 예매에 필요한 일정, 탑승자 정보 등을 입력하고 예매하기 버튼 자동 클릭 완료까지 구현했다. 결제하기 이후부터는 2탄에서 알아보기로 한다
 
 
print('모두의 파이썬')

※좋아요/댓글은 서로를 응원합니다!

728x90
반응형