赞
踩
为什么搞这个东西?【java + Selenium实现12306自动购票, 余票监测】
1.主要是12306是爬虫界的一个分水岭,所以我一直想玩12306【本次的实现并非真正意义上的破解12306实现购票,望周知】
2.一直看到微信群,朋友圈,甚至私发的携程 / 同程 购票加油包?点一下增加一个速度的那种~,想自实现一个
3.加深了解一下Selenium库的使用。【Selenium是一个自动化测试的工具库,主要用于测试,但是见仁见智,其实测试的很多东西都可以用于爬虫上面来。】
4.一直没上过12306购票,想了解一下整体购票流程,【其实是没坐过高铁~】
下拉最新代码 https://github.com/HouYuSource/spider
具体实现源在:https://github.com/HouYuSource/spider/blob/master/src/main/java/cn/shaines/spider/module/china12306/TicketWorker2.java
package cn.shaines.spider.module.china12306; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import java.util.concurrent.ThreadLocalRandom; import org.apache.commons.lang3.StringUtils; import org.openqa.selenium.By; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.Keys; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author houyu * @createTime 2019/9/2 11:17 */ public class TicketWorker2 { private static final Logger logger = LoggerFactory.getLogger(TicketWorker2.class); private static final String loginUrl = "https://kyfw.12306.cn/otn/resources/login.html"; private static final String indexUrl = "https://kyfw.12306.cn/otn/view/index.html"; private static final String searchUrl = "https://kyfw.12306.cn/otn/leftTicket/init"; private static final String confirmUrl = "https://kyfw.12306.cn/otn/confirmPassenger/initDc"; public static void main(String[] args) throws InterruptedException { // 打印初始化信息 init(); // 获取驱动 WebDriver driver = getWebDriver(); // 最大化 driver.manage().window().maximize(); // 处理登录 handleLogin(driver); // 前往搜索 handleSearch(driver); // 用户输入的车次 String carCode = handleInputCarCode(); // 处理预定 handleReserve(driver, carCode); // 处理提交 handleSubmit(driver); // 处理确认提交信息 handleConfirm(driver); // 处理提醒用户 handleCallUser(driver); // 睡眠等待用户确认信息完毕 Thread.sleep(1000000); // 关闭浏览器 driver.close(); } private static void handleCallUser(WebDriver driver) throws InterruptedException { String windowHandle = driver.getWindowHandle(); JavascriptExecutor executor = (JavascriptExecutor) driver; executor.executeScript("window.open('https://music.163.com/#/song?id=224877')"); // 切换到网易云窗口 Thread.sleep(1000); List<String> windowHandles = new ArrayList<>(driver.getWindowHandles()); driver.switchTo().window(windowHandles.get(windowHandles.size() - 1)); Thread.sleep(1000); driver.switchTo().frame("contentFrame"); Thread.sleep(1000); driver.findElement(By.cssSelector("[id=content-operation]>a:first-child")).click(); Thread.sleep(1000); driver.switchTo().window(windowHandle); } private static void handleConfirm(WebDriver driver) throws InterruptedException { Thread.sleep(1000); if(driver.findElements(By.cssSelector("#checkticketinfo_id #qr_submit_id")).size() > 0) { // 点击确认订单信息 driver.findElements(By.cssSelector("#checkticketinfo_id #qr_submit_id")).get(0).click(); } logger.debug("购票成功"); } private static void handleSubmit(WebDriver driver) throws InterruptedException { Thread.sleep(1000); driver.findElement(By.cssSelector("#normalPassenger_0")).click(); // do { if(driver.findElements(By.cssSelector("#transforNotice_id #qr_closeTranforDialog_id")).size() > 0) { // 这里有可能会出现网络繁忙的情况, 如果出现就点击确认, 关闭窗口, 然后再重试 driver.findElement(By.cssSelector("#transforNotice_id #qr_closeTranforDialog_id")).click(); } Thread.sleep(1000); driver.findElement(By.cssSelector("#submitOrder_id")).click(); // } while(driver.findElements(By.cssSelector("#transforNotice_id #qr_closeTranforDialog_id")).size() > 0); } private static void handleLogin(WebDriver driver) { driver.get(loginUrl); logger.debug("等待用户登录"); // 登录成功 WebDriverWait wait = new WebDriverWait(driver, 120); wait.until(ExpectedConditions.urlContains(indexUrl)); logger.debug("用户登录成功~"); } private static void handleReserve(WebDriver driver, String carCode) throws InterruptedException { clickSearch : for (int i = 1; ; i++) { List<WebElement> trs = driver.findElements(By.cssSelector("table tbody#queryLeftTable:first-of-type tr")); for (WebElement tr : trs) { if (tr.getAttribute("id").contains("ticket_")) { String currentCarCode = tr.findElements(By.cssSelector("td div.ticket-info > div.train a.number")).get(0).getText(); if (carCode.equals(currentCarCode)) { // 找到了车次 WebElement purchaseTd = tr.findElement(By.cssSelector("td:last-of-type")); String text = purchaseTd.getText(); logger.debug("找到预定text:" + text); List<WebElement> aElementList = purchaseTd.findElements(By.tagName("a")); if (aElementList.size() > 0) { logger.debug("点击预定:"); purchaseTd.click(); Thread.sleep(1000); if (driver.findElements(By.id("defaultwarningAlert_id")).size() > 0) { // 可能出现不可以预定时间 List<WebElement> tips = driver.findElements(By.id("content_defaultwarningAlert_hearder")); if (tips.size() > 0) { String tip = tips.get(0).getText(); logger.debug("预定失败:" + tip); } // 关闭弹窗 driver.findElement(By.id("qd_closeDefaultWarningWindowDialog_id")).click(); continue clickSearch; } // 需要处理登录问题 break clickSearch; } } } } // 没有找到车次, 或者, 点击查询按钮, 10次刷新一下页面 if (i % 10 == 0) { logger.debug("重新刷新页面"); driver.navigate().refresh(); continue; } long sleepTime = ThreadLocalRandom.current().nextLong(800, 2000); Thread.sleep(sleepTime); List<WebElement> query_ticket = driver.findElements(By.id("query_ticket")); if (query_ticket.size() > 0) { logger.debug("准备点击查询更新数据第: " + i + " 次"); query_ticket.get(0).click(); // 等待页面数据出来 WebDriverWait wait = new WebDriverWait(driver, 120); wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("table tbody#queryLeftTable:first-of-type tr"))); } else { logger.debug("查询按钮不可用"); } } } private static String handleInputCarCode() { System.err.println("=============================请在下方输入你需要抢购的车次=================================="); String carCode = null; try (Scanner scanner = new Scanner(System.in)) { while (StringUtils.isEmpty(carCode)) { System.out.println(); carCode = scanner.nextLine(); } } System.out.println("您输入的车次为:" + carCode); return carCode; } private static void handleSearch(WebDriver driver) { driver.get(searchUrl); System.out.println(); System.err.println("请在打开的浏览器页面中填写 '出发地' '目的地' '出发日' 等相关信息, 并且点击 '查询' 按钮完成本次操作"); // 获取搜索的条数 WebDriverWait wait = new WebDriverWait(driver,120); wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("table tbody#queryLeftTable:first-of-type tr"))); List<WebElement> trs = driver.findElements(By.cssSelector("table tbody#queryLeftTable:first-of-type tr")); List<String> currentCarCodeList = new ArrayList<>(16); for (WebElement tr : trs) { if (tr.getAttribute("id").contains("ticket_")) { String currentCarCode = tr.findElements(By.cssSelector("td div.ticket-info > div.train a.number")).get(0).getText(); currentCarCodeList.add(currentCarCode); } } System.out.println("本次查找到车次数量为: " + currentCarCodeList.size()); for (int i = 0; i < currentCarCodeList.size(); i++) { String currentCarCode = currentCarCodeList.get(i); System.out.print(currentCarCode + "\t||\t"); if (i % 5 == 1) { System.out.println(); } } } private static void init() { System.out.println(); System.out.println("‖======================= 欢迎使用 12306 抢票助手 ========================‖"); System.out.println("‖ ‖"); System.out.println("‖ 使用过程中如果有任何问题欢迎反馈 ‖"); System.out.println("‖ mail : for.houyu@foxmail.com ‖"); System.out.println("‖ version : V1.0 ‖"); System.out.println("‖ powered by houyu ‖"); System.out.println("‖ ‖"); System.out.println("‖======================= 欢迎使用 12306 抢票助手 ========================‖"); System.out.println(); System.out.println(); System.out.println(); } private static WebDriver getWebDriver() { // System.setProperty("webdriver.chrome.driver", "C:\\install\\chromedriver\\chromedriver.exe");// chromedriver地址 System.setProperty("webdriver.chrome.driver", "src/main/resources/chromedriver.exe");// chromedriver地址 WebDriver driver = new ChromeDriver(); // 新建一个WebDriver 的对象,但是new 的是谷歌的驱动 return driver; } }
安装google浏览器(谷歌内核的浏览器, 版本76.0.3809.132+)
结合程序控制台输出,进行浏览器操作等
‖======================= 欢迎使用 12306 抢票助手 ========================‖ ‖ ‖ ‖ 使用过程中如果有任何问题欢迎反馈 ‖ ‖ mail : for.houyu@foxmail.com ‖ ‖ version : V1.0 ‖ ‖ powered by houyu ‖ ‖ ‖ ‖======================= 欢迎使用 12306 抢票助手 ========================‖ Starting ChromeDriver 76.0.3809.126 (d80a294506b4c9d18015e755cee48f953ddc3f2f-refs/branch-heads/3809@{#1024}) on port 11230 Only local connections are allowed. Please protect ports used by ChromeDriver and related test frameworks to prevent access by malicious code. [1568739524.884][WARNING]: Timed out connecting to Chrome, retrying... 九月 18, 2019 12:58:47 上午 org.openqa.selenium.remote.ProtocolHandshake createSession 信息: Detected dialect: W3C [1568739529.158][WARNING]: Timed out connecting to Chrome, retrying... 00:58:52.142 [main] DEBUG cn.shaines.spider.module.china12306.TicketWorker2 - 等待用户登录 ... ...
优点
弊端
使用python实现代码级别的破解【可以了解github: 12306 / py12306】
整体流程都看了一遍,发现12306也有几个难点,
其中最难的应该是登录验证码,人看点击都输入错误好几次【这个可接入开源12306打码进行破解】,
其次是请求车次数据的解析,返回的数据有点乱,但是还是有很多可阅读数据的,还没深入DEBUG看,但是感觉不是很难的那种混淆,
除此之外尚未发现比价复杂的点,等闲下来有时间可以玩玩~~
如果你也是一名爬虫爱好者,或者有了解过12306的相关爬虫,欢迎我们一起讨论
blog: https://shaines.cn
mail : for.houyu@foxmail.com
csdn: https://blog.csdn.net/jinglongsou
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。