爬虫之短信验证码
处理短信验证码的思路就是手机端有一个可以转发短信到我们可以读取的地方。目前我的处理方式是将短信转发到邮箱,再读取邮箱中的邮件拿取验证码。
首先就需要下载转发工具:https://github.com/pppscn/SmsForwarder/releases/tag/v3.2.0
具体使用参考官方文档。
以下是12306 短信验证码测试案例
爬取类,有两种方式:一种是使用cookie访问,还有就是登录,也可以结合使用
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.jsoup.Jsoup;
import org.openqa.selenium.By;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.LocalTime;
import java.util.*;
/**
* @Author 没有梦想的java菜鸟
* @Date 2024/1/9 16:59
* @Version 1.0
*/
@Slf4j
public class Selenium12306 {
public static final String LOGIN_URL = "https://kyfw.12306.cn/otn/resources/login.html";
public static final String URL = "https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc";
public static String DATE = "";
public static String FROM_STATION = "";
public static String TO_STATION = "";
public static String USERNAME = "";
public static String PASSWORD = "";
public static String PASSENGER = "";
public static String ID_CARD = "";
public static FirefoxDriver driver = null;
static {
try {
Properties pro = new Properties();
pro.load(new InputStreamReader(Objects.requireNonNull(Selenium12306.class.getClassLoader().getResourceAsStream("config.properties")), StandardCharsets.UTF_8));
DATE = pro.getProperty("date");
FROM_STATION = pro.getProperty("startStation");
TO_STATION = pro.getProperty("endStation");
USERNAME = pro.getProperty("username");
PASSWORD = pro.getProperty("password");
PASSENGER = pro.getProperty("passenger");
ID_CARD = pro.getProperty("idCard");
driver = catchConfig();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
doPurchase();
}
public static void doPurchase() throws Exception {
driver.get(URL);
Thread.sleep(1000);
log.info("跳转到选票页面");
buyTicket();
}
private static void addCookie(Set<Cookie> cookies) {
cookies.forEach(
cookie -> {
driver.manage().addCookie(cookie);
}
);
}
private static void addCookie() {
String cookies = "\n";
String[] split = cookies.split(";");
for (String s : split) {
String[] split1 = s.split("=");
Cookie cookie = new Cookie(split1[0].trim(), split1[1].trim());
driver.manage().addCookie(cookie);
}
}
private static boolean doLogin() throws InterruptedException {
WebDriverWait wait = new WebDriverWait(driver, 5);
WebElement username = driver.findElement(By.id("J-userName"));
WebElement password = driver.findElement(By.id("J-password"));
username.sendKeys(USERNAME);
log.info("输入用户名:{}", USERNAME);
password.sendKeys(PASSWORD);
log.info("输入密码:{}", PASSWORD);
driver.findElement(By.id("J-login")).click();
//验证码
WebElement idCardEle = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("id_card")));
Thread.sleep(200L);
idCardEle.sendKeys(ID_CARD);
log.info("输入身份证号后4为:{}", ID_CARD);
WebElement codeButton = driver.findElement(By.id("verification_code"));
codeButton.click();
return codeCheck();
}
private static void buyTicket() throws InterruptedException {
val startStation = driver.findElement(By.id("fromStationText"));
startStation.click();
startStation.sendKeys(FROM_STATION);
startStation.sendKeys(Keys.ENTER);
log.info("出发站点输入完成");
Thread.sleep(500L);
val endStation = driver.findElement(By.id("toStationText"));
endStation.clear();
endStation.click();
endStation.sendKeys(TO_STATION);
endStation.sendKeys(Keys.ENTER);
log.info("到达站点输入完成");
Thread.sleep(500L);
val startDate = driver.findElement(By.id("train_date"));
startDate.click();
startDate.clear();
startDate.sendKeys(DATE);
log.info("出发日期输入完成");
Thread.sleep(500L);
driver.findElement(By.id("cc_train_type_btn_all")).findElement(By.xpath("//input[@value='G']")).click();
log.info("选择高铁");
Map<String, String> map = new HashMap<>();
while (true) {
log.info("查票");
driver.findElement(By.id("query_ticket")).click();
Thread.sleep(1000L);
boolean flag = true;
List<WebElement> trCollection = null;
try {
// 显示全部预定车次
driver.findElement(By.id("avail_ticket")).click();
log.info("显示全部预定车次");
trCollection = driver.findElement(By.id("queryLeftTable")).findElements(By.tagName("tr"));
} catch (Exception e) {
flag = false;
}
if (flag) selectTicket(map, trCollection);
if (CollUtil.isNotEmpty(map)) {
log.info("购票成功,开始发送成功邮件");
EmailListener.sendEmail("抢票成功:</br>" +
"出发时间:" + map.get("startTime") + "</br>" +
"到达时间:" + map.get("endTime") + "</br>" +
"请尽快登录12306完成支付");
break;
}
// 随机在1分钟到2分钟之间sleep
long sleep = (long) (Math.random() * 60000 + 60000);
log.info("没有查询到车票,{}秒后重新查询", sleep / 1000);
Thread.sleep(sleep);
}
}
private static void isLogin() {
try {
driver.findElementById("J-login");
driver.findElementById("J-userName");
boolean b = doLogin();
Thread.sleep(1000L);
if (!b) {
log.info("登录失败,开始重试第1次");
Thread.sleep(40000L);
if (!codeCheck()) {
EmailListener.sendEmail("登录失败,请检查短信是否转发");
}
}
} catch (Exception e) {
}
}
private static void selectTicket(Map<String, String> map, List<WebElement> trCollection) {
try {
WebElement ticket = trCollection.stream().filter(
tr -> {
// 有隐藏的单元格 排除无内容的
if (StrUtil.isEmpty(tr.getText())) return false;
String timeStr = tr.findElement(By.className("start-t")).getText();
LocalTime startTime = LocalTime.of(9, 0);
LocalTime endTime = LocalTime.of(14, 0);
LocalTime time = LocalTime.parse(timeStr);
boolean hasTicket = tr.findElements(By.tagName("td")).get(3).getText().equals("有");
return time.isAfter(startTime) && time.isBefore(endTime) && hasTicket;
}
).findFirst().get();
String startTime = ticket.findElement(By.className("start-t")).getText();
String endTime = ticket.findElement(By.className("color999")).getText();
// 点击预定按钮
ticket.findElement(By.className("btn72")).click();
log.info("点击预定按钮");
// 是否弹出登录
Thread.sleep(1500);
isLogin();
val dom = Jsoup.parse(driver.getPageSource());
if (dom.getElementById("qd_closeDefaultWarningWindowDialog_id") != null) {
driver.findElement(By.id("qd_closeDefaultWarningWindowDialog_id")).click();
}
val liCollection =
driver.findElement(By.id("normal_passenger_id")).findElements(By.tagName("li"));
List<String> passengerList = Arrays.asList(PASSENGER.split(","));
liCollection.forEach(info -> {if (passengerList.contains(info.findElement(By.tagName("label")).getText())) info.findElement(By.tagName("label")).click();});
Thread.sleep(500);
driver.findElement(By.id("submitOrder_id")).click();
Thread.sleep(2000);
map.put("startTime", startTime);
map.put("endTime", endTime);
driver.findElement(By.id("qr_submit_id")).click();
} catch (InterruptedException e) {
}
}
private static boolean codeCheck() {
WebElement codeInput = driver.findElement(By.id("code"));
String code = EmailListener.getCode();
log.info("获取到的验证码为:{}", code);
codeInput.sendKeys(code);
WebElement sureButton = driver.findElement(By.id("sureClick"));
sureButton.click();
String message = driver.findElementById("message").findElement(By.tagName("p")).getText();
System.out.println(message);
return !message.contains("错误") && !message.contains("异常");
}
private static FirefoxDriver catchConfig() throws InterruptedException {
System.setProperty("webdriver.gecko.driver", "D:\\app\\WebDriver\\geckodriver-v0.31.0-win64\\geckodriver.exe");
FirefoxOptions options = new FirefoxOptions();
FirefoxProfile profile = new FirefoxProfile();
// 设置火狐浏览器路径
options.setBinary("D:\\app\\firefox\\firefox.exe");
//禁止GPU渲染
options.addArguments("--disable-gpu");
// options.addArguments("--headless");
//忽略错误
options.addArguments("ignore-certificate-errors");
//禁止浏览器被自动化的提示
options.addArguments("--disable-infobars");
//反爬关键:window.navigator.webdrive值=false*********************
options.addPreference("dom.webdriver.enabled", false);
//设置请求头
profile.setPreference(
"general.useragent.override",
"Mozilla/5.0(iPhone;CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML,like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"
);
return new FirefoxDriver(options);
}
}
读取邮件类
import cn.hutool.core.date.DateUtil;
import lombok.extern.slf4j.Slf4j;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.search.FromTerm;
import javax.mail.search.SearchTerm;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static java.util.stream.Collectors.toList;
@Slf4j
public class EmailListener {
public static String EMAIL = "";
public static String AUTH_CODE = "";
public static String RECEIVE_EMAIL = "";
static {
try {
Properties pro = new Properties();
pro.load(new InputStreamReader(Objects.requireNonNull(EmailListener.class.getClassLoader().getResourceAsStream("config.properties")), StandardCharsets.UTF_8));
EMAIL = pro.getProperty("email");
AUTH_CODE = pro.getProperty("authCode");
RECEIVE_EMAIL = pro.getProperty("receiveEmail");
} catch (IOException e) {
e.printStackTrace();
}
}
public static String getCode() {
// 等待30秒
try {
log.info("等待20秒短信转发到邮件");
Thread.sleep(20000L);
} catch (InterruptedException e) {
}
Properties properties = new Properties();
properties.setProperty("mail.store.protocol", "imaps");
properties.setProperty("mail.imaps.host", "imap.qq.com");
properties.setProperty("mail.imaps.port", "993");
Session session = Session.getDefaultInstance(properties);
try {
// 连接到邮箱
Store store = session.getStore();
store.connect(EMAIL, AUTH_CODE);
// 打开收件箱
Folder inbox = store.getFolder("INBOX");
inbox.open(Folder.READ_ONLY);
SearchTerm sender = new FromTerm(new InternetAddress());
Message[] messages = inbox.search(sender);
Message mess = Arrays.stream(messages).filter(
message -> {
try {
Date sentDate = message.getSentDate();
if (sentDate == null) return false;
MimeMultipart content = (MimeMultipart) message.getContent();
BodyPart bodyPart = content.getBodyPart(0);
String textContent = (String) bodyPart.getContent();
return sentDate.after(DateUtil.parse("2024-01-01 00:00:00"))
&& "验证".equals(message.getSubject()) && textContent.contains("12306");
} catch (Exception e) {
return false;
}
}
).sorted(Comparator.comparing((Message message) -> {
try {
return message.getSentDate().getTime();
} catch (MessagingException e) {
e.printStackTrace();
return Long.MAX_VALUE;
}
}).reversed()).collect(toList()).get(0);
MimeMultipart content = (MimeMultipart) mess.getContent();
BodyPart bodyPart = content.getBodyPart(0);
String textContent = (String) bodyPart.getContent();
String[] split = textContent.split("验证码:");
String[] split1 = split[1].split(",");
return split1[0];
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
public static void sendEmail(String content) {
try {
Properties props = new Properties();
// 基于smtp
props.setProperty("mail.transport.protocol", "smtp");
props.setProperty("mail.smtp.host", "smtp.qq.com");
props.setProperty("mail.smtp.auth", "true");
Session session = Session.getInstance(props);
Transport transport = session.getTransport();
transport.connect(EMAIL, AUTH_CODE);
MimeMessage message = createMessage(session, EMAIL, RECEIVE_EMAIL,content);
transport.sendMessage(message, message.getAllRecipients());
// 关闭连接
transport.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private static MimeMessage createMessage(Session session, String email, String receiveEmail,String content) throws Exception {
// 创建邮件对象
MimeMessage message = new MimeMessage(session);
// 发件人
message.setFrom(new InternetAddress(email, "没有梦想的java菜鸟", "UTF-8"));
// 收件人
message.setRecipient(MimeMessage.RecipientType.TO, new InternetAddress(receiveEmail, "User", "UTF-8"));
// 邮件主题
message.setSubject("12306购票结果", "UTF-8");
// 邮件正文
message.setContent(content, "text/html;charset=UTF-8");
// 邮件的发送日期
message.setSentDate(new Date());
// 保存前面的设置
message.saveChanges();
return message;
}
}
配置文件内容
date=2024-01-11
startStation=起始站
endStation=终点站
username=用户名
password=密码
passenger=乘车人
idCard=身份证后四位
email=邮箱
authCode=邮箱授权码
receiveEmail=收件邮箱
需要的依赖
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.141.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
<scope>compile</scope>
</dependency>
<!--hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>