采集机器人主要应用采集部分网站数据,但是目前私自爬取部分网站数据可能涉及违法,请谨慎使用。
主要使用的是打包运行的方式,因此使用main方法的形式
主流程主流程主要为,参数初始化;启动浏览器登录;开启循环采集;采集结束,发送错误信息并睡眠;
public class RpaRobot {
public static void start(){
// 初始化参数配置
RpaRobotConfig.init();
LogManager.logInfo("rpa客户端启动中...");
// 配置初始化,启动浏览器,登录
LogInOutHandler.openBrowserStart();
LogManager.logInfo("rpa客户端启动成功...");
while (true){
// 错误信息列表
List
基础类构建
RPA上下文
@Data
public class RpaContext {
// 当前浏览器
private WebDriver driver;
// 加载的文件配置
private JSONConfig config;
}
配置类
@Data
public class JSONConfig {
private String openUrl = "";
private String companyName = "";
private String phoneNumber = "";
private String password = "";
private String checkFactoringUrl = "";
private String saveFactoringUrl = "";
private String dingURL = "";
private String saveLogUrl;
private String logDingURL;
}
运行配置(可忽略)
@Data
public class RunConfig {
private String invokeTime;
private long lastTime;
private long nextTime = 0L;
private String runTaskName;
private long runTaskStartTime;
private long runTaskEndTime;
private int errorIndex;
// 一次任务循环间隔
private long timeInterva;
// 某一批次明细是否采集
private boolean collection;
private boolean down;
private boolean approve;
private long pushIndex;
private long rpaStart;
public String getLogFileUrl() {
try {
String fileUrl = FileWriteTools.fileDir + "/"
+ new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + ".txt";
return fileUrl;
} catch (Exception e) {
return "";
}
}
}
浏览器工具
// 浏览器工具
public class BrowserTools {
// 私有化工具类
private BrowserTools() {
}
public static Logger logger = LoggerFactory.getLogger(BrowserTools.class);
public static WebDriver chromDriver() {
WebDriver driver = new ChromeDriver();
return driver;
}
public static WebDriver internetExplorerDriver(boolean wait) {
DesiredCapabilities ieCapabilities = DesiredCapabilities.internetExplorer();
// 启用 {@link #FORCE_CREATE_PROCESS} 时定义使用的 IE CLI 切换的功能
ieCapabilities.setCapability(InternetExplorerDriver.IE_SWITCHES, "-private");
// 定义在操作期间使用本机事件还是 JavaScript 事件的能力
ieCapabilities.setCapability(InternetExplorerDriver.NATIVE_EVENTS, false);
// 定义在 IEDriverServer 启动期间忽略非浏览器保护模式设置的能力
ieCapabilities.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS, true);
// 定义在 IEDriverServer 启动 IE 之前清理或不清理浏览器缓存的能力
ieCapabilities.setCapability(InternetExplorerDriver.IE_ENSURE_CLEAN_SESSION, true);
// 启用此功能以默认接受所有 SSL(安全套接字协议) 证书
ieCapabilities.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true);
ieCapabilities.setJavascriptEnabled(true);
// 需要窗口焦点
ieCapabilities.setCapability("requireWindowFocus", true);
// 不启用持久悬停
ieCapabilities.setCapability("enablePersistentHover", false);
if (wait) {
// 页面加载策略
ieCapabilities.setCapability("pageLoadStrategy", "none");
}
WebDriver driver = new InternetExplorerDriver(ieCapabilities);
return driver;
}
public static WebDriver webDriver(String name) {
String[] names = name.replace(",", ",").split(",");
if ("ie".equals(names[0])) {
if (names.length > 1) {
return internetExplorerDriver("no".equals(names[1]));
} else {
return internetExplorerDriver(false);
}
} else if ("chrom".equals(names[0])) {
return chromDriver();
}
return null;
}
}
加载系统变量(浏览器等)
public class LoadSystemTools {
private static final String JACOB_DLL_PATH = "C:/Windows/System32/jacob-1.19-x64.dll";
private static final String SAVE_FILE_URL = "plug/SaveIEFile.exe";
public static Logger logger = LoggerFactory.getLogger(LoadSystemTools.class);
public static String getRootPath() {
String path = "";
try {
path = URLDecoder.decode(LoadSystemTools.class.getResource("/").getPath().replaceFirst("/", ""), "utf-8");
} catch (Exception e) {
throw new RuntimeException("解析URL异常");
}
return path;
}
public static void loadSystem() {
// 用于加载系统中的变量
System.setProperty(LibraryLoader.JACOB_DLL_PATH, JACOB_DLL_PATH);
// 运行
// String iePath = getRootPath() + "drivers/IEDriverServer.exe";
// 打包
String iePath = "drivers/IEDriverServer.exe";
System.setProperty("webdriver.ie.driver", iePath);
// 运行
// String chromPath = getRootPath() + "drivers/chromedriver.exe";
// 打包
String chromPath = "drivers/chromedriver.exe";
System.setProperty("webdriver.chrome.driver", chromPath);
}
public static void loadSystemForTest(){
System.setProperty(LibraryLoader.JACOB_DLL_PATH, JACOB_DLL_PATH);
System.setProperty("webdriver.ie.driver", "src/main/resources/drivers/IEDriverServer.exe");
System.setProperty("webdriver.chrome.driver", "src/main/resources/drivers/chromedriver.exe");
}
public static String getSaveIEFileUrl() {
return getRootPath() + SAVE_FILE_URL;
}
操作类
public class OperateHandler {
private static final String ID = "id";
private static final String XPATH = "xpath";
private static final String click = "click";// 点击
private static final String sendKeys = "sendKeys";// 赋值
private static final String getText = "getText";// 获取值
private static final String getAttribute = "getAttribute";// 获取input值
public static void robotOperationNoException(String methodKey, String methodVal, String operation, String param, int errorRetryTime){
try {
robotOperation(methodKey, methodVal, operation, param, errorRetryTime);
} catch (Exception e) {
LogManager.logError(null, "robotOperationNoException->异常");
}
}
public static String robotOperation(String methodKey, String methodVal, String operation, CharSequence param) {
return robotOperation(methodKey, methodVal, operation, param, 3);
}
public static String robotOperation(String methodKey, String methodVal, String operation, CharSequence param, int errorRetryTime) {
RpaContext cxt = RobotFactoring.getCxt();
boolean isGoOn = true;
int i = 0;
String res = "";
while (isGoOn) {
try {
switch (operation) {
case click:
click(findElement(methodKey, methodVal));
isGoOn = false;
break;
case sendKeys:
findElement(methodKey, methodVal).sendKeys(param);
isGoOn = false;
break;
case getText:
res = findElement(methodKey, methodVal).getText();
isGoOn = false;
break;
case getAttribute:
res = findElement(methodKey, methodVal).getAttribute("value");
isGoOn = false;
break;
default:
isGoOn = false;
}
ThreadTools.sleepMillis(50);
} catch (Exception e) {
i++;
ThreadTools.sleepMillis(300);
if (i > errorRetryTime) {
LogManager.logError(null, "执行参数:operation=" + operation +",methodKey=" + methodKey
+ ",methodVal=" + methodVal + ",param=" + param + ", 执行次数=" + (i-1));
throw new RuntimeException(e.getMessage());
}
}
}
return res;
}
public static WebElement findElement(String methodKey, String methodVal) {
RpaContext cxt = RobotFactoring.getCxt();
WebElement ele = cxt.getDriver().findElement(locator(methodKey, methodVal));
return ele;
}
public static void click(WebElement webElement) {
try {
webElement.click();
} catch (TimeoutException e) {
e.printStackTrace();
} catch (Exception e) {
throw e;
}
}
public static List findElements(String methodKey, String methodVal) throws Exception {
RpaContext cxt = RobotFactoring.getCxt();
List elements = null;
try {
elements = cxt.getDriver().findElements(locator(methodKey, methodVal));
} catch (Exception e) {
LogManager.logError(e, "查询多个元素异常,methodKey:" + methodKey + ", methodVal:" + methodVal);
}
return elements;
}
public static boolean elementExist(String methodKey, String methodVal) {
try {
RobotFactoring.getCxt().getDriver().findElement(locator(methodKey, methodVal));
return true;
} catch (Exception e) {
return false;
}
}
public static boolean elementsExist(String methodKey, String methodVal) {
try {
List elements = RobotFactoring.getCxt().getDriver().findElements(locator(methodKey, methodVal));
if (CollectionUtils.isEmpty(elements)) {
return false;
}
return true;
} catch (Exception e) {
return false;
}
}
private static By locator(String methodKey, String methodVal) {
if ("id".equals(methodKey)) {
return By.id(methodVal);
} else if ("name".equals(methodKey)) {
return By.name(methodVal);
} else if ("className".equals(methodKey)) {
return By.className(methodVal);
} else if ("tagName".equals(methodKey)) {
return By.tagName(methodVal);
} else if ("linkText".equals(methodKey)) {
return By.linkText(methodVal);
} else if ("partialLinkText".equals(methodKey)) {
return By.partialLinkText(methodVal);
} else if ("xpath".equals(methodKey)) {
return By.xpath(methodVal);
} else if ("css".equals(methodKey)) {
return By.cssSelector(methodVal);
} else {
return null;
}
}
public static void close(WebDriver driver){
driver.close();
// cmd 关闭应用
Runtime rt = Runtime.getRuntime();
try {
// rt.exec("cmd.exe /C start /b taskkill /f /t /im iexplore.exe /im chrome.exe /im IEDriverServer.exe /im chromedriver.exe");
rt.exec("cmd.exe /C start /b taskkill /f /t /im iexplore.exe /im IEDriverServer.exe");
Thread.sleep(3000L);
} catch (Exception e) {
e.printStackTrace();
}
}
}
参数初始化
public class RpaRobotConfig {
public static void init() {
LoadSystemTools.loadSystem();
RpaContext cxt = new RpaContext();
ExcelConfig config = new ExcelConfig();
RunConfig runConfig = new RunConfig();
runConfig.setInvokeTime(new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
runConfig.setRunTaskName("RPA启动流程");
try {
InputStream is = FactoringRpaRobotConfig.class.getResourceAsStream("/config/config.json");
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
String s="";
String configContentStr = "";
try {
while((s=br.readLine())!=null) {
configContentStr = configContentStr+s;
}
} catch (IOException e) {
e.printStackTrace();
}
JSONConfig jsonConfig = JSONObject.parseObject(configContentStr, JSONConfig.class);
cxt.setConfig(jsonConfig);
cxt.setRunConfig(runConfig);
RobotFactoring.setCxt(cxt);
} catch (Exception e) {
LogManager.logError(e,"rpa初始化异常");
}
}
}
打开浏览器,登录
public class LogInOutHandler {
public static void openBrowserStart() {
RpaContext cxt = RobotFactoring.getCxt();
try {
quitDriver(cxt.getDriver());
WebDriver driver = BrowserTools.internetExplorerDriver(false);
RobotFactoring.getCxt().setDriver(driver);
cxt.setDriver(driver);
// 打开浏览器
driver.manage().window().maximize();
System.out.println("准备打开页面");
// 访问页面
driver.navigate().to(cxt.getConfig().getOpenUrl());
} catch (Exception e) {
LogManager.logError(e, "openBrowserStart登录异常");
}
int loginTimes = 1;
// 首次登录,登录等待时间为4秒
startLogin(cxt,loginTimes,RobotConstants.LOGIN_WAIT_TIME);
}
private static void quitDriver(WebDriver driver) {
try {
if (driver != null) {
LogManager.logInfo("quitDriver->开启清理老版浏览器进程");
driver.close();
driver.quit();
Runtime.getRuntime().exec("taskkill /F /IM IEDriverServer.exe");
}
} catch (Exception e) {
LogManager.logError(e, "quitDriver->浏览器关闭异常");
}
// 老版浏览器清理后,等待10秒再次开启新的旅程
ThreadTools.sleep(10);
}
public static void startLogin(RpaContext cxt,int loginTimes, int loginWaitTime) {
ThreadTools.sleep(2);
try {
login(cxt,loginWaitTime);
// 登录后检查登录状态
if (checkLoginStatus(cxt)) {
// 登录成功打印日志
LogManager.getInstance().loginSuccess();
} else {
// 登录失败则重新访问页面重新开始登录流程
// cxt.getDriver().get(cxt.getConfig().getOpenUrl());
loginTimes++;
// 登录失败等待时间+1秒
loginWaitTime++;
if (loginTimes >= 5) {
// 关闭浏览器
OperateHandler.close(cxt.getDriver());
LogManager.logInfo("连续五次登录失败,准备重新打开浏览器,尝试登录");
// 重新打开浏览器
openBrowserStart();
} else {
LogManager.getInstance().loginFailure("登录失败,尝试重新登录,当前登录次数:" + loginTimes);
ThreadTools.sleep(2);
// 重新开始登录流程 ,递归
startLogin(cxt,loginTimes,loginWaitTime);
}
}
} catch (Exception e) {
LogManager.logError(e, "startLogin登录异常");
ThreadTools.sleep(2);
}
}
public static boolean checkLoginStatus(RpaContext cxt) {
// 元素 首页 存在,则为true
if (clickFirstPageValidate()) {
return true;
}
try {
// 若不存在,则重新访问页面
cxt.getDriver().get(cxt.getConfig().getOpenUrl());
} catch (Exception e) {
LogManager.logError(e, "重新访问页面失败" + e.getMessage());
// 访问页面发生异常,则关闭浏览器
OperateHandler.close(cxt.getDriver());
ThreadTools.sleep(2);
// 重新打开浏览器
FactoringLogInOutHandler.openBrowserStart();
}
ThreadTools.sleep(3);
// 检测是否存在密码框
if (OperateHandler.elementExist(RobotConstants.ID, "password")) {
LogManager.logInfo("checkLoginStatus->检测到登录密码框,需要重新登录");
ThreadTools.sleep(2);
return false;
}
// 递归
return checkLoginStatus(cxt);
}
public static boolean clickFirstPageValidate() {
try {
// 首页按钮是否存在
boolean elementExist = OperateHandler.elementExist(RobotConstants.XPATH, "//div[@class='header']/div[2]/ul/a[1]");
if (elementExist) {
// 若存在则点击验证
OperateHandler.robotOperation(RobotConstants.XPATH, "//div[@class='header']/div[2]/ul/a[1]", RobotConstants.click, null);
return true;
}
} catch (Exception e) {
LogManager.logError(null, "建信保理首页无法点击");
}
return false;
}
public static void login(RpaContext cxt, int loginWaitTime) {
// 输入公司名
OperateHandler.robotOperationNoException(RobotConstants.XPATH, "//input[@name='name']", RobotConstants.sendKeys,
cxt.getConfig().getCompanyName(), 1);
ThreadTools.sleepMillis(100);
// 输入手机号
OperateHandler.robotOperationNoException(RobotConstants.XPATH, "//input[@name='mobile']", RobotConstants.sendKeys,
cxt.getConfig().getPhoneNumber(), 1);
ThreadTools.sleepMillis(100);
// 输入密码
OperateHandler.robotOperationNoException(RobotConstants.ID, "password", RobotConstants.sendKeys,
cxt.getConfig().getPassword(), 1);
ThreadTools.sleepMillis(100);
// 点击登录
OperateHandler.robotOperationNoException(RobotConstants.ID, "loginSubmit", RobotConstants.click,
cxt.getConfig().getPassword(), 1);
ThreadTools.sleep(loginWaitTime);
}
public static boolean logout(RpaContext cxt){
try {
// 判断 展示退出登录是否存在
if (OperateHandler.elementExist(RobotConstants.XPATH,
"//div[@class='logout ivu-dropdown']//div[@class='ivu-dropdown-rel']//i[@class='ivu-icon ivu-icon-ios-arrow-down']")) {
// 若存在,则将鼠标移动到该位置
WebElement element = cxt.getDriver().findElement(By.xpath(
"//div[@class='logout ivu-dropdown']//div[@class='ivu-dropdown-rel']//i[@class='ivu-icon ivu-icon-ios-arrow-down']"));
Actions action = new Actions(cxt.getDriver());
action.moveToElement(element);
} else {
return false;
}
ThreadTools.sleep(1);
// 判断退出登录按钮是否存在
if (OperateHandler.elementExist(RobotConstants.XPATH,
"//div[@class='ivu-select-dropdown logout-dropdown']//ul[@class='ivu-dropdown-menu']//li[@class='ivu-dropdown-item']")) {
// 若存在,使用js进行点击操作(因为该元素隐藏,driver.click()无法进行操作)
WebElement logout = cxt.getDriver().findElement(By.xpath(
"//div[@class='ivu-select-dropdown logout-dropdown']//ul[@class='ivu-dropdown-menu']//li[@class='ivu-dropdown-item']"));
JavascriptExecutor js = (JavascriptExecutor) cxt.getDriver();
js.executeScript("arguments[0].click()", logout);
ThreadTools.sleep(3);
} else {
return false;
}
// 判断确认按钮是否存在
if (OperateHandler.elementExist(RobotConstants.XPATH,
"//div[@class='modal-footer']//button[@class='ivu-btn ivu-btn-primary']")) {
// 点击确认
OperateHandler.click(cxt.getDriver()
.findElement(By.xpath("//div[@class='modal-footer']//button[@class='ivu-btn ivu-btn-primary']")));
} else {
return false;
}
return true;
} catch (Exception e) {
LogManager.logInfo("logout->退出登录失败");
return false;
}
}
}
采集
采集过程要注意的是,每次采集后,如果发生了跳转其他页面的情况,那么driver就会发生变化,当本页数据采集完,再返回之前页面,元素就会失效。获取当前页的数个融资编号,然后根据融资编号点击对应详情按钮,采集完毕返回后,需要重新获取融资编号。
若需要持续性的采集,那么在代码中就需要对所有可能出现的异常进行处理,防止运行时因为异常导致中断。
package com.banksteel.finance.rpa.factoring;
import com.alibaba.fastjson.JSONObject;
import com.banksteel.finance.rpa.config.ExcelConfig;
import com.banksteel.finance.rpa.config.RobotFactoring;
import com.banksteel.finance.rpa.config.RpaContext;
import com.banksteel.finance.rpa.constant.RobotConstants;
import com.banksteel.finance.rpa.log.LogManager;
import com.banksteel.finance.rpa.tools.DateUtil;
import com.banksteel.finance.rpa.tools.HttpClientUtil;
import com.banksteel.finance.rpa.tools.StringTools;
import com.banksteel.finance.rpa.tools.ThreadTools;
import com.google.gson.JsonObject;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.openqa.selenium.By;
import org.openqa.selenium.InvalidElementStateException;
import org.openqa.selenium.Keys;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.springframework.util.StringUtils;
public class FactoringCollectHandler {
private static ExcelConfig excelConfig;
private static WebDriver driver;
private static final int RETRY_MAX_TIME = 5;
public static void factoringCollect(List
睡眠
睡眠方法在采集类的最下面,睡眠结束后需要检查当前系统状态,是否属于登录状态(检查首页按钮是否存在),若不是,则重新登录
使用总结主要使用的内容为
WebDriver driver= BrowserTools.internetExplorerDriver(false);
driver.findElement
| 策略 | 语法 | 描述 |
|---|---|---|
| By id | driver.findElement(By.id()) | 通过id属性定位元素 |
| By name | driver.findElement(By.name()) | 通过name属性定位元素 |
| By class name | driver.findElement(By.className()) | 通过class属性定位元素 |
| By tag name | driver.findElement(By.tagName()) | 通过HTML标签名定位元素 |
| By link text | driver.findElement(By.linkText()) | 通过链接内容定位元素 |
| By partial link text | driver.findElement(By.partialLinkText()) | 通过部分链接内容定位元素 |
| By css | driver.findElement(By.cssSelector()) | 通过css选择器定位元素 |
| By xpath | driver.findElement(By.Xpath()) | 通过xpath定位元素 |
主要记录一下xpath的语法
| 表达式 | 描述 |
|---|---|
| nodename | 选取此节点的所有子节点。 |
| / | 从根节点选取(取子节点)。 |
| // | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置(取子孙节点)。 |
| . | 选取当前节点。 |
| … | 选取当前节点的父节点。 |
| @ | 选取属性。 |
| 路径表达式 | 结果 |
|---|---|
| bookstore | 选取 bookstore 元素的所有子节点。 |
| /bookstore | 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径! |
| bookstore/book | 选取属于 bookstore 的子元素的所有 book 元素。 |
| //book | 选取所有 book 子元素,而不管它们在文档中的位置。 |
| bookstore//book | 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。 |
| //@lang | 选取名为 lang 的所有属性。 |
| 路径表达式 | 结果 |
|---|---|
| /bookstore/book[1] | 选取属于 bookstore 子元素的第一个 book 元素。 |
| /bookstore/book[last()] | 选取属于 bookstore 子元素的最后一个 book 元素。 |
| /bookstore/book[last()-1] | 选取属于 bookstore 子元素的倒数第二个 book 元素。 |
| /bookstore/book[position()❤️] | 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。 |
| //title[@lang] | 选取所有拥有名为 lang 的属性的 title 元素。 |
| //title[@lang=‘eng’] | 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。 |
| /bookstore/book[price>35.00] | 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。 |
| /bookstore/book[price>35.00]//title | 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。 |
| 通配符 | 描述 |
|---|---|
| * | 匹配任何元素节点。 |
| @* | 匹配任何属性节点。 |
| node() | 匹配任何类型的节点。 |
| 路径表达式 | 结果 |
|---|---|
| /bookstore/* | 选取 bookstore 元素的所有子元素。 |
| //* | 选取文档中的所有元素。 |
| //title[@*] | 选取所有带有属性的 title 元素。 |
文本选择,文本选择看似方便,其实有一定可能选取失败,具体原因不是很清楚。
//span[text()='预计']
使用 // 进行选取时,若选取不到,可将更多上级写出,以便选取,例如上方例子可写成
driver.findElement(
By.xpath("//div[@class='mt20 bg-F9FAFB content-style ivu-row']//span[text()='预计']"));
记录(登出按钮是隐藏状态,鼠标悬停才会展示登出,如何登出)
当按钮是隐藏状态的时候,使用click无法对齐进行操作,需要使用JavascriptExecutor进行点击操作
// 获取该元素(需要悬停的元素)
WebElement element = driver.findElement(By.xpath());
// 移动鼠标到该元素
Actions action = new Actions(driver);
action.moveToElement(element);
// 获取登出元素
WebElement logout = driver.findElement(By.xpath());
// 使用JavascriptExecutor执行点击
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("arguments[0].click()", logout);



