2024년 6월, 코레일 홈페이지가 대대적으로 리뉴얼되면서 기존 자동 예매 코드가 더 이상 동작하지 않게 되었습니다.
이 글에서 소개했던 방식이 막히면서, 새롭게 구조를 뜯어고치고, 최신 Selenium과 Spring Boot로 다시 구현했습니다.
코레일 홈페이지 리뉴얼에 대응하기
서울에 살면서 지방에 내려갈 때마다 좌석이 매진되는 일이 종종 있는데 그래서 크롬에서 지원되는 KTX 매크로를 찾아보았는데, 이게 지원이 종료된 건지… 안된다.
그래서 내가 만들어서 쓰자.. 라고 생각이 들어 바로 실행에 옮겨보았습니다 ! (불법적인 목적 ❌)
Selenium을 사용해서 간단하게 KTX 자동 예매 프로그램을 만들었습니다.
로컬에서 서버 실행 후 /swagger-ui 로 이동하셔서 값들 넣어서 실행해주시면 됩니다!

TicketController): 사용자 상호 작용을 위한 REST API 엔드포인트 제공@GetMapping("/ticket/reserve"): 이 엔드포인트은 티켓 예약 프로세스를 트리거합니다. 사용자 로그인 정보, 열차 정보, 시간과 같은 매개변수를 허용합니다.TicketService): Korail 웹사이트와 상호작용하기 위한 브라우저 자동화를 포함한 비즈니스 로직을 처리initializeWebDriver(): 해당 메서드는 WebDriver를 설정합니다.loginUser(): 코레일 웹사이트 로그인 과정을 자동화합니다.navigateToReservationPage() 및 checkAndReserveTicket(): 해당 메소드는 예약 페이지로 이동한 다음 지정된 시간 내에 사용 가능한 티켓을 지속적으로 확인합니다.setTicketSearchCriteria(), findAndReserveAvailableTicket() 및 handleReservationModal(): 검색 매개변수 설정, 예약 가능한 티켓 찾기 및 KTX-산천의 경우, 예약 시 모달 처리를 통해 예약 프로세스를 지원합니다.SwaggerConfig): API 문서를 자동으로 생성하도록 Swagger를 구성합니다.application.properties
springdoc.swagger-ui.path = /swagger
springdoc.swagger-ui.tags-sorter=alpha
springdoc.swagger-ui.operations-sorter=alpha
springdoc.api-docs.path=/api-docs
springdoc.default-consumes-media-type=application/json;charset=UTF-8
springdoc.default-produces-media-type=applicionation/json;charset=UTF-8
// Web driver initialize
private WebDriver initializeWebDriver() {
// Chrome Driver
WebDriverManager.chromedriver().setup();
// Chrome option : headless
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless");
WebDriver driver = new ChromeDriver(options);
// Safari Drvier
// System.setProperty("webdriver.safari.driver", "/System/Cryptexes/App/usr/bin/safaridriver");
// WebDriver driver = new SafariDriver();
driver.manage().window().setSize(new Dimension(1600, 1200));
return driver;
}
웹 드라이버는 Chrome, Safari Driver를 사용하였습니다.
💡 Safari Driver의 경우, 직접 설치하여 실행하여야 합니다. (Safari의 경우 headless 모드를 지원하지 않습니다.)
💡 headless 모드 사용을 하지 않을 경우는 옵션부분을 지우면 됩니다.
💡 Selenium 4.0 버전 이상부터는 WebDriverManager를 사용하면 드라이버가 저절로 실행됩니다.
private WebElement findReservationButton(WebDriver driver, int index) {
try {
WebElement regularSeatElement = driver.findElement(
By.xpath("//*[@id='tableResult']/tbody/tr[" + index + "]/td[6]"));
WebElement specialSeatElement = driver.findElement(
By.xpath("//*[@id='tableResult']/tbody/tr[" + index + "]/td[5]"));
WebElement regularSeatImg = regularSeatElement.findElement(By.tagName("img"));
WebElement specialSeatImg = specialSeatElement.findElement(By.tagName("img"));
String altRegular = regularSeatImg.getAttribute("alt");
String altSpecial = specialSeatImg.getAttribute("alt");
if (altRegular.equals("예약하기") && altSpecial.equals("예약하기")) {
return regularSeatElement.findElement(By.tagName("a"));
} else if (altRegular.equals("예약하기")) {
return regularSeatElement.findElement(By.tagName("a"));
} else if (altSpecial.equals("예약하기")) {
return specialSeatElement.findElement(By.tagName("a"));
} else {
return null;
}
} catch (NoSuchElementException e) {
return null;
}
}
좌석 예매의 경우엔, 일반석 예매를 우선으로 하였습니다.
💡 (입석+좌석)는 예매가 안됩니다.
💡 예매 과정에서 좌석 선택은 수동으로 하여야하기 때문에, 자동으로 좌석이 선택됩니다.
private void handleReservationModal(WebDriver driver, WebDriverWait wait) {
WebElement modal = driver.findElement(By.id("korail-modal-traininfo"));
if (modal.isDisplayed()) {
WebElement iframe = modal.findElement(By.tagName("iframe"));
driver.switchTo().frame(iframe);
WebElement continueButton = wait.until(
ExpectedConditions.elementToBeClickable(By.cssSelector(".cont p.btn_c a")));
continueButton.click();
driver.switchTo().defaultContent();
}
}
기존의 KTX의 경우엔 모달창이 따로 뜨진 않지만, KTX -산천의 경우엔 모달창이 뜹니다.
모달창의 확인 버튼을 누르지 않으면 예약이 되지 않기 때문에, 해당 로직을 추가로 넣어주었습니다 !
⚠️ 해당 프로젝트는 개인적인 용도로만 사용하여 주시고, 불법적인 거래나 행위는 엄격히 금지됩니다.
본 프로젝트 사용 시 관련 법률, 규정 및 서비스 약관을 준수하는 것은 사용자의 책임입니다.
본 프로젝트의 사용으로 인한 어떠한 문제에 대해서도 본 프로젝트 개발자는 책임을 지지 않습니다.
이 글을 읽고 셀레니움 크롤링을 공부하는 계기가 되었습니다 감사합니다