銀行卡很多,每次查看餘額多要進入每個銀行網站進行查看,於是就想到如何通過程序自動獲取,網上查了些資料,一般用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總體來說並不複雜,主要還是要看你爬取信息的邏輯,以及路徑,然後處理好容錯,難點在驗證碼及第三方插件的操作。