为什么搞这个东西?【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 : [email protected] ‖");
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 : [email protected]
csdn: https://blog.csdn.net/jinglongsou