java + Selenium实现12306自动购票

8 篇文章 1 订阅

为什么搞这个东西?【java + Selenium实现12306自动购票, 余票监测】
1.主要是12306是爬虫界的一个分水岭,所以我一直想玩12306【本次的实现并非真正意义上的破解12306实现购票,望周知】
2.一直看到微信群,朋友圈,甚至私发的携程 / 同程 购票加油包?点一下增加一个速度的那种~,想自实现一个
3.加深了解一下Selenium库的使用。【Selenium是一个自动化测试的工具库,主要用于测试,但是见仁见智,其实测试的很多东西都可以用于爬虫上面来。】
4.一直没上过12306购票,想了解一下整体购票流程,【其实是没坐过高铁~】

首先看看订单生成的效果

在这里插入图片描述

了解一下12306整体购票流程

在这里插入图片描述

了解一下本次程序的大概流程

  • 【___程序】打开浏览器进入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 - 等待用户登录
    ...
    ...
    

Selenium的实现的购票程序优缺点

优点

  • 流程简单,快速实现
  • 页面可视化,有感知

弊端

  • 效率低,性能差【相对而言,其实在这购票上面,你也不敢刷得很快,因为都是实名制的,小心进入黑名单,虽然也有办法规避】
  • 网站购票指南改变,程序不可用
  • 未知问题无法掌握【有可能出现乱七八糟的弹窗都会导致整体的程序挂掉】

规划

  • 实现脱离java环境运行,也就是打包为一个.exe文件等形式
  • 进行模块化,组件化,方便日后维护以及打补丁等

理想发展

使用python实现代码级别的破解【可以了解github: 12306 / py12306】

  • 自动打码【网上有开源针对12306的验证码深度学习库,据说准确率高达98%】
  • 自动登录
  • 无限刷票
  • 自动下单
  • 自动支付
  • 订单通知

整体流程都看了一遍,发现12306也有几个难点,
其中最难的应该是登录验证码,人看点击都输入错误好几次【这个可接入开源12306打码进行破解】,
其次是请求车次数据的解析,返回的数据有点乱,但是还是有很多可阅读数据的,还没深入DEBUG看,但是感觉不是很难的那种混淆,
除此之外尚未发现比价复杂的点,等闲下来有时间可以玩玩~~

最后的话

  • 本程序最后验证时间是:2019-09-17
  • 本程序切勿使用商业用途
  • 程序尚未使用IP池,切勿多实例(启动多次)购票
  • 不会帮助购买票的~但是你在使用过程中遇到什么问题欢迎一起讨论
  • 没有义务帮忙解决一切问题

如果你也是一名爬虫爱好者,或者有了解过12306的相关爬虫,欢迎我们一起讨论
blog: https://shaines.cn
mail : for.houyu@foxmail.com
csdn: https://blog.csdn.net/jinglongsou

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值