• 【Selenium】WebDriverPool让动态爬虫变得更简单高效稳定


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

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

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

    code

    pom.xml
    		
    		<dependency>
    			<groupId>org.seleniumhq.seleniumgroupId>
    			<artifactId>selenium-javaartifactId>
    			<version>4.4.0version>
    		dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    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 ;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    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();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    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();
                        }
                    }
                }
            }
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    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();
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
  • 相关阅读:
    测试辅助工具(抓包工具)的使用1 之初识抓包工具(fiddler)
    Web阶段一 静态网页
    【面试题】Vue2动态添加路由 router.addRoute()
    安科瑞煤矿电力监控系统的研究与应用
    pytorch案例代码-2
    Qt基础 界面镜像
    odi资料库查询sql
    [ 网络基础篇 ] MAP 迈普交换机常用命令详解
    ARM 汇编指令集——汇编中三种符号(汇编指令、伪指令、伪操作)、汇编基本格式、数据操作指令、跳转指令、特殊功能寄存器操作指令、内存操作指令、混合编程
    【每日一题】2760. 最长奇偶子数组-2023.11.16
  • 原文地址:https://blog.csdn.net/qq_33594101/article/details/126781790