一个根据场景自行封装WebDriver池子而已(此处以chrome为例)

下载指定版本的谷歌浏览器和chromedriver

下载地址:http://chromedriver.storage.googleapis.com/index.html
如下图:谷歌浏览器版本为106.0.5249.61的chromedriver版本
在这里插入图片描述

code

pom.xml
		<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
		<dependency>
			<groupId>org.seleniumhq.selenium</groupId>
			<artifactId>selenium-java</artifactId>
			<version>4.4.0</version>
		</dependency>
application.properties
webdriver.driverPath=C:\\devtool\\chromedriver\\chromedriver105.0.5195.52\\chromedriver.exe
webdriver.chromepath=C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe
webdriver.maxConnections=5
webdriver.minConnections=5
webdriver.initConnections=5
webdriver.conninterval=1000
webdriver.timeout=5000
WebDriverBean.java (配置类)
import lombok.Data;

/**
 * @author zhouzhiqiang
 * @version 1.0
 * @date 2022-09-23 15:37
 */
@Data
public class WebDriverBean {

    /**
     * 浏览器驱动路径
     */
    private String driverPath;

    /**
     * chrome路径
     */
    private String chromepath;

    /**
     * 连接池最大连接数
     */
    private int maxConnections ;
    /**
     * 连接池最小连接数
     */
    private int minConnections;
    /**
     * 连接池初始连接数
     */
    private int initConnections;
    /**
     * 重连间隔时间 ,单位毫秒
     */
    private int conninterval ;
    /**
     * 获取连接超时时间 ,单位毫秒,0永不超时
     */
    private int timeout ;
}
IWebDriverPool.java (驱动池核心接口)
import org.openqa.selenium.WebDriver;

/**
 * @author zhouzhiqiang
 * @version 1.0
 * @date 2022-09-23 15:33
 */
public interface IWebDriverPool {

    /**
     * 获取一个浏览器驱动,如果等待超过超时时间,将返回null
     *
     * @return 浏览器驱动对象
     */
    WebDriver getWebDriver();

    /**
     * 获得当前线程的连接库连接
     *
     * @return 浏览器驱动对象
     */
    WebDriver getCurrentConnecton();

    /**
     * 释放当前线程浏览器驱动
     *
     * @param driver 浏览器驱动对象
     */
    void releaseWebDriver(WebDriver driver);

    /**
     * 销毁清空当前驱动连接池
     */
    void destroy();

    /**
     * 连接池可用状态
     *
     * @return 连接池是否可用
     */
    boolean isActive();

    /**
     * 定时器,检查连接池
     */
    void checkPool();

    /**
     * 获取线程池活动连接数
     *
     * @return 线程池活动连接数
     */
    int getActiveNum();

    /**
     * 获取线程池空闲连接数
     *
     * @return 线程池空闲连接数
     */
    int getFreeNum();

}
WebDriverPool.java (驱动池核心实现)

需要注意的是代理ip,如果有ip反爬的需要自定义策略重新初始化驱动池更新代理ip


import com.kdniao.logisticsfront.stocrack.biz.task.storoutecrack.proxy.ProxyUtil;
import org.apache.commons.lang.StringUtils;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.TimerTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


/**
 * @author zhouzhiqiang
 * @version 1.0
 * @date 2022-09-23 15:34
 */
public class WebDriverPool implements IWebDriverPool {

    private static final Logger logger = LoggerFactory.getLogger(WebDriverPool.class);

    private WebDriverBean webDriverBean = null;

    private ProxyUtil proxyUtil = null;

    /**
     * 驱动池可用状态
     */
    private Boolean isActive = true;

    /**
     * 空闲驱动池,由于读写操作较多,所以使用linklist
     */
    private LinkedList<WebDriver> freeWebDriver = new LinkedList<>();
    /**
     * 活动驱动池,由于读写操作较多,所以使用linklist
     */
    private LinkedList<WebDriver> activeWebDriver = new LinkedList<>();

    /**
     * 当前线程获得的连接
     */
    private ThreadLocal<WebDriver> currentWebDriver = new ThreadLocal<>();

    private WebDriverPool() {
        super();
    }

    public static WebDriverPool createWebDriverPool(WebDriverBean webDriverBean, ProxyUtil proxyUtil) {
        WebDriverPool webDriverPool = new WebDriverPool();
        webDriverPool.webDriverBean = webDriverBean;
        webDriverPool.proxyUtil = proxyUtil;
        for (int i = 0; i < webDriverPool.webDriverBean.getInitConnections(); i++) {
            try {
                //获取web驱动
                WebDriver driver = webDriverPool.getInitWebDriver();
                webDriverPool.freeWebDriver.add(driver);
            } catch (Exception e) {
                logger.error("驱动池初始化失败" + e.getMessage());
                return null;
            }
        }
        webDriverPool.isActive = true;
        return webDriverPool;
    }

    /**
     * 获取web驱动
     * @return
     */
    private WebDriver getInitWebDriver() {
        if (this.webDriverBean.getDriverPath() != null && this.webDriverBean.getDriverPath().length() > 1) {
            System.setProperty("webdriver.chrome.driver", this.webDriverBean.getDriverPath());
        }
        ChromeOptions chromeOptions = getChromeOptions(this.webDriverBean, this.proxyUtil);
        WebDriver driver = new ChromeDriver(chromeOptions);
        //浏览器最大化
        driver.manage().window().maximize();
        driver.get("https://www.sto.cn/Service/CustomerService?active_li=2&active_span=21");
        return driver;
    }

    /**
     * 检查驱动是否存活
     *
     * @param webDriver
     * @return
     */
    private Boolean isValidWebDriver(WebDriver webDriver) {

        try {
            if (webDriver == null) {
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

    private WebDriver newWebDriver() {
        WebDriver webDriver = null;
        try {
            if (this.webDriverBean != null) {
                //获取web驱动
                webDriver = getInitWebDriver();
            }
        } catch (Exception e) {
            logger.error("创建新的驱动失败");
        }
        return webDriver;
    }

    @Override
    public synchronized WebDriver getWebDriver() {
        WebDriver webDriver = null;
        if (this.getActiveNum() < this.webDriverBean.getMaxConnections()) {
            if (this.getFreeNum() > 0) {
                logger.info("空闲池中剩余驱动数为" + this.getFreeNum() + ",直接获取驱动");
                webDriver = this.freeWebDriver.pollFirst();
                this.activeWebDriver.add(webDriver);
            } else {
                logger.info("空闲池中无驱动,创建新的驱动");
                try {
                    webDriver = this.newWebDriver();
                    this.activeWebDriver.add(webDriver);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } else {
            logger.info("当前已达最大驱动数");
            return null;

        }
        return webDriver;
    }

    @Override
    public synchronized WebDriver getCurrentConnecton() {
        WebDriver webDriver = this.currentWebDriver.get();
        try {
            if (!isValidWebDriver(webDriver)) {
                webDriver = this.getWebDriver();
            }
        } catch (Exception e) {
            logger.error("获取当前驱动失败" + e.getMessage());
        }
        return webDriver;
    }

    @Override
    public synchronized void releaseWebDriver(WebDriver driver) {
        logger.info(Thread.currentThread().getName() + "关闭连接:activeWebDriver.remove :" + driver);
        this.activeWebDriver.remove(driver);
        this.currentWebDriver.remove();
        try {
            if (isValidWebDriver(driver)) {
                freeWebDriver.add(driver);
            } else {
                freeWebDriver.add(this.newWebDriver());
            }
        } catch (Exception e) {
            logger.error("释放当前驱动失败" + e.getMessage());
        }
        this.notifyAll();
    }

    @Override
    public synchronized void destroy() {
        for (WebDriver webDriver : this.freeWebDriver) {
            try {
                if (isValidWebDriver(webDriver)) {
                    webDriver.quit();
                }
            } catch (Exception e) {
                logger.error(e.getMessage());
            } finally {
                webDriver.quit();
            }
        }
        for (WebDriver webDriver : this.activeWebDriver) {
            try {
                if (isValidWebDriver(webDriver)) {
                    webDriver.quit();
                }
            } catch (Exception e) {
                logger.error(e.getMessage());
            } finally {
                webDriver.quit();
            }
        }
        this.isActive = false;
        this.freeWebDriver.clear();
        this.activeWebDriver.clear();
        logger.info("驱动池已经摧毁");
    }

    @Override
    public boolean isActive() {
        return this.isActive;
    }

    @Override
    public void checkPool() {
        ScheduledExecutorService ses = new ScheduledThreadPoolExecutor(2);
        ses.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                logger.info("空闲驱动数" + getFreeNum());
                logger.info("活动驱动数" + getActiveNum());
            }
        }, 1, 30, TimeUnit.SECONDS);
        ses.scheduleAtFixedRate(new checkFreepools(this), 1, 5, TimeUnit.SECONDS);
    }

    @Override
    public int getActiveNum() {
        return this.activeWebDriver.size();
    }

    @Override
    public int getFreeNum() {
        return this.freeWebDriver.size();
    }


    /**
     * 驱动池内部要保证指定最小数量的驱动数
     */
    class checkFreepools extends TimerTask {
        private WebDriverPool webDriverPool = null;

        public checkFreepools(WebDriverPool wp) {
            this.webDriverPool = wp;
        }

        @Override
        public void run() {
            if (this.webDriverPool != null && this.webDriverPool.isActive()) {
                int poolstotalnum = webDriverPool.getFreeNum()
                        + webDriverPool.getActiveNum();
                int subnum = webDriverPool.webDriverBean.getMinConnections()
                        - poolstotalnum;

                if (subnum > 0) {
                    logger.info("扫描并维持空闲池中的最小驱动数,需补充" + subnum + "个驱动");
                    for (int i = 0; i < subnum; i++) {
                        try {
                            webDriverPool.freeWebDriver
                                    .add(webDriverPool.newWebDriver());
                        } catch (Exception e) {
                            logger.error("补充驱动失败" + e.getMessage());
                        }
                    }
                }
            }
        }

    }

    /**
     * 获取浏览器配置
     *
     * @return
     */
    private static ChromeOptions getChromeOptions(WebDriverBean webDriverBean, ProxyUtil proxyUtil) {
        System.setProperty("webdriver.chrome.driver", webDriverBean.getDriverPath());
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--disable-logging");
        // 字符编码 utf-8 支持中文字符
        options.addArguments("lang=zh_CN.UTF-8");
        // 设置容许弹框
        options.addArguments("disable-infobars", "disable-web-security");
        // 驱动自动控制软件标识
        options.addArguments("--disable-blink-features=AutomationControlled");
        options.setExperimentalOption("excludeSwitches", new String[]{"enable-automation"});
        // 设置无gui 开发时仍是不要加,能够看到浏览器效果
        options.addArguments("--headless");
        options.addArguments("--disable-gpu");//禁止gpu渲染
        options.addArguments("--no-sandbox");//关闭沙盒模式
        options.addArguments("--disable-dev-shm-usage");
        options.addArguments("--incognito"); // 隐身模式(无痕模式)
        options.addArguments("--disable-extensions"); // disabling extensions

        //禁用日志
        options.addArguments("--log-level=3");
        options.addArguments("--silent");

        HashMap<String, Object> prefs = new HashMap<>();
        prefs.put("profile.default_content_settings", 2);
        options.setExperimentalOption("prefs", prefs);
        options.addArguments("blink-settings=imagesEnabled=false");//禁用图片
        options.setBinary(webDriverBean.getChromepath());//手动指定使用的浏览器位置

        //设置代理
        String proxyIP = proxyUtil.getProxyIP(true);
        if (StringUtils.isNotEmpty(proxyIP)) {
            Proxy proxy = new Proxy().setHttpProxy(proxyIP).setSslProxy(proxyIP);
            options.setProxy(proxy);
        }

        return options;
    }
}
WebDriverManager.java (使用驱动)

驱动使用的时候会出现以下场景(当前线程中断线程池未正常释放,存在并发场景及线程池重建措施来使线程池有效的被使用及内存使用的稳定----由于时间问题未做进一步的优化,使用到了同步这就不是最佳实践了,有兴趣的小伙伴可以尝试使用TransmittableThreadLocal线程副本实现哈,性能可提升一个档次)


import com.kdniao.logisticsfront.stocrack.biz.task.storoutecrack.proxy.ProxyUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.openqa.selenium.WebDriver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.time.Duration;
import java.time.LocalDateTime;

/**
 * @author zhouzhiqiang
 * @version 1.0
 * @date 2022-09-23 15:36
 */
public class WebDriverManager {

    private static final Logger logger = LoggerFactory.getLogger(WebDriverManager.class);

    @Autowired
    private WebDriverBean webDriverBean;

    @Autowired
    private ProxyUtil proxyUtil;

    private static volatile WebDriverPool webDriverPool = null;

    private LocalDateTime lastDestroyTime = LocalDateTime.now();

    public WebDriverPool createWebDriverPool() {
        if (webDriverPool == null) {
            synchronized (WebDriverManager.class) {
                if (webDriverPool == null) {
                    webDriverPool = WebDriverPool.createWebDriverPool(webDriverBean, proxyUtil);
                }
            }
        }
        return webDriverPool;
    }

    public WebDriver getWebDriver() {
        if (webDriverPool == null) {
            this.createWebDriverPool();
        }
        WebDriver webDriver = null;
        synchronized (WebDriverManager.class) {
            webDriver = webDriverPool.getWebDriver();
            if (ObjectUtils.isEmpty(webDriver)) {
                destroy();
                createWebDriverPool();
            }
        }
        return webDriver;
    }

    public void closeWebDriver(WebDriver driver) {
        if (driver != null && webDriverPool != null) {
            webDriverPool.releaseWebDriver(driver);
        }
    }

    public void destroy() {
        //每分钟只允许销毁一次
        if (webDriverPool != null && Duration.between(lastDestroyTime, LocalDateTime.now()).getSeconds() > 60) {
            synchronized (WebDriverManager.class) {
                if (webDriverPool != null && Duration.between(lastDestroyTime, LocalDateTime.now()).getSeconds() > 60) {
                    try {
                        webDriverPool.destroy();
                        webDriverPool = null;
                    } finally {
                        lastDestroyTime = LocalDateTime.now();
                    }
                }
            }
        }
    }

}

WebDriverPoolTest.java
import org.openqa.selenium.WebDriver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

import java.util.ArrayList;
import java.util.List;

/**
 * @author zhouzhiqiang
 * @version 1.0
 * @date 2022-09-23 15:56
 */
public class WebDriverPoolTest{

    @Autowired
    private WebDriverManager webDriverManager;

    public void test() {
        try {
            List<Thread> threadlist = new ArrayList<Thread>();
            for (int i = 1; i <= 9; i++) {
                Thread subThread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        WebDriver webDriver = webDriverManager.getWebDriver();
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        webDriverManager.closeWebDriver(webDriver);
                    }
                }, "thread name" + i);
                subThread.start();
                threadlist.add(subThread);
            }
            Thread.sleep(10000);
            webDriverManager.destroy();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐