银行卡很多,每次查看余额多要进入每个银行网站进行查看,于是就想到如何通过程序自动获取,网上查了些资料,一般用selenium做自动化测试,然后就学了下selenium,写了建行、招行、平安、陆金所,不过招行网站已不允许查,只能在手机进行账单查询。
环境:Jdk1.8、selenium3.14、Hibernate3、Spring2
逻辑:
使用selenium最重要的一点就是要找到获取信息元素的路径,逻辑比较简单,先使用WebDriver调用firefox(这里使用ff驱动geckodriver,也可以使用其他浏览器的驱动)打开登录界面,自动输入用户名、密码,获取登录,然后跳转到要获取的信息界面,按元素路径获取数据,再进行处理入库。
登录一般会碰到验证码的问题,目前也没找到较好的识别手段,就用等待的方式人工输入,有些网站是可以通过浏览器缓存的方式,在第一次登录正确后,后续登录就不需要再输入验证码了,如陆金所,这种方式可以先设置FirefoxProfile,在Mac下可以在终端窗口输入:/Applications/Firefox.app/Contents/MacOS/firefox-bin -ProfileManager,调出firefox配置界面,这里新建了一个”banks”配置,然后点“启动Firefox”,打开界面后,正确登录陆金所,在程序调用的时候使用该配置,就不用输验证码了。包括第一次登录时会出现的一些帮助界面,也可以通过这种方式避免。
建行的登录现在会发验证码到手机,这种情况就只能在该界面等待用户输入后再进行后续操作了。
接下来就是你找元素路径然后对数据进行处理了,以下为建行代码:
package cn.com.selen.bank;
import java.util.List;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoAlertPresentException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;
import cn.com.selen.domain.Accountinfo;
import cn.com.selen.util.ThreadUtil;
public class Ccb extends BankingBase {
public Ccb(){
BANK_NAME = "ccb";
LOGIN_URL = "https://ibsbjstar.ccb.com.cn/CCBIS/V6/common/login.jsp";
ACCOUNT_URL = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_05?SERVLET_NAME=B2CMainPlat_05&CCB_IBSVersion=V6&PT_STYLE=1";
}
@Override
protected void login(String userName, String pwd) {
//CCB的cookie好像没用,每次就直接登录
try {
WebElement fclogin = driver.findElement(By.id("fQRLGIN"));
fclogin.click();
driver.switchTo().frame(fclogin);
driver.findElement(By.id("USERID")).click();
driver.findElement(By.id("USERID")).sendKeys(""); //用户名
driver.findElement(By.id("LOGPASS")).click();
driver.findElement(By.id("LOGPASS")).sendKeys(""); //密码
Thread.sleep(5000);
driver.findElement(By.id("loginButton")).click();
(new WebDriverWait(driver, 10)).until(new ExpectedCondition<Boolean>() {
public Boolean apply(WebDriver d){
return !d.getCurrentUrl().equals(LOGIN_URL);
}
});
ThreadUtil.sleep(1000); //接收短信界面
try {
Alert alert = driver.switchTo().alert();
String alertText = alert.getText();
System.out.println("弹出数据:"+ alertText);
alert.accept();
} catch(NoAlertPresentException e1){
e1.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
driver.switchTo().defaultContent();
}
public void updateCash(int id){
//如果有介绍界面,先关闭
if(cookiesUtil.isElementPresent(driver,By.id("show1"))){
WebElement show1 = driver.findElement(By.id("show1"));
if(show1.isDisplayed() && cookiesUtil.isElementPresent(driver,By.id("Map1"))){//显示界面,且找到关闭按钮
JavascriptExecutor jse = (JavascriptExecutor)driver;
//jse.executeScript("closeMengBan()");
WebElement eMap = driver.findElement(By.xpath("//*[@id=\"Map1\"]/area[1]"));
jse.executeScript("arguments[0].click()", eMap);
}
}
ThreadUtil.sleep(1000);
driver.switchTo().defaultContent();
while(!this.cookiesUtil.isElementPresent(driver, By.xpath("//*[@id=\"per1\"]/a"))) {
ThreadUtil.sleep(5000);
}
ThreadUtil.sleep(5000);//5s时间点掉弹出界面
//点查询,进入查询界面
WebElement f = driver.findElement(By.xpath("//*[@id=\"per1\"]/a/i"));
f.click();
ThreadUtil.sleep(5000);
driver.switchTo().frame("txmainfrm");
driver.switchTo().frame("result");
List<WebElement> cards;
do {
ThreadUtil.sleep(1000);
cards = driver.findElements(By.xpath("//*[@id=\"scrollPic\"]/ul/li"));
}while(cards.size()<1);
for(int i=0;i<cards.size()-1;i++){//最后一个是隐藏的,不需要处理
WebElement card = cards.get(i);
card.click();
ThreadUtil.sleep(8000);
String cardInfo = card.getAttribute("values");
String[] cardInfos = cardInfo.split("\\|"); //格式:卡号|330000000|xyk|多币种|马**||浙江省|30||身份证号
driver.switchTo().frame("result"); //切换到取数区
WebElement money = null;//找到结果显示的Table
if(cardInfos[2].equals("xyk")){//信用卡
money = driver.findElement(By.xpath("//*[@class=\"one_lines_table\"]/tbody/tr[1]/td[5]/span"));
}else if(cardInfos[2].equals("dkzh")){//贷款卡
money = driver.findElement(By.xpath("//*[@class=\"one_lines_table\"]/tbody/tr[2]/td[4]/span"));
}else{//储蓄卡及其他
if(cardInfos[4] == null || cardInfos[4].length()==0){//自己的账号会显示多币种
money = driver.findElement(By.xpath("//*[@class=\"one_lines_table\"]/tbody/tr[3]/td[5]/span"));
}else{
money = driver.findElement(By.xpath("//*[@class=\"one_lines_table\"]/tbody/tr/td[5]/span"));
}
}
String strMoney = money.getAttribute("innerHTML");
int ind = strMoney.indexOf("</script>");
if(ind>0){
strMoney = strMoney.substring(ind+9);
}
strMoney = strMoney.replace(",", "");
double dblMoney = Double.parseDouble(strMoney);
Accountinfo act = bank.findByCode(cardInfos[0]);//从数据库里查找账号信息
if(act.getType() ==2 || act.getType() == 5)dblMoney=-dblMoney;
System.out.println(cardInfos[0] +":"+dblMoney);
bank.saveCost(act.getId(), dblMoney);
driver.switchTo().parentFrame();
if((i+1)%4 == 0 ){
driver.findElement(By.id("RightArr")).click();
ThreadUtil.sleep(3000);
}
}
sumCost(1); //这里1为父账号ID,这里表示建行的,自己设定的
}
}
代码是几年前写的,有些结构可能设计不大合理,Seleium总体来说并不复杂,主要还是要看你爬取信息的逻辑,以及路径,然后处理好容错,难点在验证码及第三方插件的操作。