Selenium ChromeDriver 截图标记指定元素的方法


selenium_logo_large.png

phantomjs 是无头浏览器的代表,可以截全屏的图,对于标记元素来说是很简单的;不过最新的 Selenium 版本表示不再支持;所以只能使用其他的代理品; 正好 chromeFirefox 等都推出无头模式,这里就使用 ChromeDriver 作为演示
ChromeDriver 通过设置 setHeadless(true) 既可开启无头模式

设置 ChromeDirver 必要的参数

//-------------------------
// 该方法有同事提供 -> 滑稽脸
//-------------------------

private static ChromeOptions initWebOption(String proxy) {
    ChromeOptions chromeOptions = new ChromeOptions();
    // 开启无头模式
    chromeOptions.setHeadless(true);

    //基础参数设置
    chromeOptions.addArguments("--silent");
    chromeOptions.addArguments("--no-sandbox");
    chromeOptions.addArguments("--disable-gpu");
    chromeOptions.addArguments("--disable-dev-shm-usage");
    chromeOptions.addArguments("--ignore-certificate-errors");
    chromeOptions.addArguments("--allow-running-insecure-content");

    //优化参数设置
    chromeOptions.addArguments("--incognito");
    chromeOptions.addArguments("--disable-images");
    chromeOptions.addArguments("--start-maximized");
    chromeOptions.addArguments("--disable-plugins");
    chromeOptions.addArguments("--disable-infobars");
    chromeOptions.addArguments("--lang=zh_CN.UTF-8");
    chromeOptions.addArguments("--disable-javascript");
    chromeOptions.addArguments("--window-size=1928,1080");
    //chromeOptions.addArguments("--auto-open-devtools-for-tabs");

    //UserAgent
    String userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.3239.132 Safari/537.36";
    chromeOptions.addArguments("--user-agent=" + userAgent);
    log.info("[初始驱动参数] 正在设置驱动用户代理: {}", userAgent);

    //设置加载策略
    String pageLoadStrategy = "eager";
    chromeOptions.setCapability("pageLoadStrategy", pageLoadStrategy);
    log.info("[初始驱动参数] 正在设置驱动加载策略: {}", pageLoadStrategy);

    //高级参数设置
    Map<String, Object> chromePrefs = new HashMap<>();
    //禁止密码保存
    chromePrefs.put("credentials_enable_service", false);
    chromePrefs.put("profile.password_manager_enabled", false);
    //禁止网页弹窗
    chromePrefs.put("profile.default_content_settings.popups", 0);
    //禁止加载图片
    chromePrefs.put("profile.managed_default_content_settings.images", 2);
    chromeOptions.setExperimentalOption("prefs", chromePrefs);
    //屏蔽控制显示
    chromeOptions.setExperimentalOption("useAutomationExtension", false);
    //突破网站检测
    chromeOptions.setExperimentalOption("excludeSwitches", Collections.singletonList("enable-automation"));

    //网络代理设置
    if (proxy == null || proxy.length() == 0) {
        return chromeOptions;
    }
    Proxy netProxy = new Proxy();
    netProxy.setHttpProxy(proxy);
    chromeOptions.setProxy(netProxy);
    return chromeOptions;
}

截屏并标记特定元素

 public void screenshotAndMark(
            // Chrome Driver
            WebDriver webDriver,
            // 同一个页面需要标记的元素
            List<WebElement> elements,
            // 自定义截图的名称
            Function<WebElement, String> nameMapping,
            // 接受最后的结果
            Consumer<Optional<List<ScreenshotMark>>> consumer
) {
    if (webDriver == null) {
        logger.warn("WebDriver is null and screenshot failed.");
        if (consumer != null) {
            consumer.accept(Optional.empty());
        }
        return;
    }
    if (elements == null || elements.isEmpty()) {
        logger.warn("elements is empty and screenshot failed.");
        if (consumer != null) {
            consumer.accept(Optional.empty());
        }
        return;
    }
    // JS 脚本执行器
    JavascriptExecutor executor = ((JavascriptExecutor) webDriver);

    // 结果
    List<ScreenshotMark> screenshotMarks = new ArrayList<>();
    for (WebElement webElement : elements) {
        // 元素原始位置
        Point location = webElement.getLocation();

        int x = location.x;
        int y = location.y;
        // 滚动元素到可视区域
        // 并设置误差 10px
        executor.executeScript("window.scrollTo(arguments[0], arguments[1])", x - 10, y - 10);


        // 元素大小
        Dimension size = webElement.getSize();
        int width = size.getWidth();
        int height = size.getHeight();

        String name = nameMapping != null ? nameMapping.apply(webElement) : webElement.getText();

        if (width == 0 || height == 0) {
            System.out.println(name + " width eq zero or height eq zero, ignored.");
            continue;
        }
        if (!webElement.isDisplayed()) {
            System.out.println(name + " is not display, ignored.");
            continue;
        }


        // 获取当前滚动条的位置
        Object offsetYObj = executor.executeScript("return document.body.scrollTop || document.documentElement.scrollTop");
        Object offsetXObj = executor.executeScript("return document.body.scrollLeft || document.documentElement.scrollLeft");

        // 计算元素的偏移位置
        int offsetY = 0;
        int offsetX = 0;
        if (offsetYObj != null) {
            try {
                offsetY = Integer.parseInt(offsetYObj.toString());
            } catch (Exception e) {
                // ignore
            }
        }
        if (offsetXObj != null) {
            try {
                offsetX = Integer.parseInt(offsetXObj.toString());
            } catch (Exception e) {
                // ignore
            }
        }
        WebDriver augmentedDriver = new Augmenter().augment(webDriver);

        // 不同元素展示的结果不同
        // 所以每一次都需要截屏
        byte[] screenshotBytes = ((TakesScreenshot) augmentedDriver).getScreenshotAs(OutputType.BYTES);

        try (
                ByteArrayInputStream bytes = new ByteArrayInputStream(screenshotBytes);
                ByteArrayOutputStream result = new ByteArrayOutputStream()
        ) {
            // 转换为图片
            BufferedImage screenshot = ImageIO.read(bytes);
            // 画笔
            Graphics2D graphics = screenshot.createGraphics();

            // 设置“抗锯齿”的属性
            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            // 画笔颜色
            graphics.setColor(Color.RED);
            // 画笔粗细
            graphics.setStroke(new BasicStroke(4f));

            // 绘制矩形标记
            graphics.drawRect(x - offsetX, y - offsetY, width, height);

            // 输出
            ImageIO.write(screenshot, "PNG", result);

            byte[] screenshotMarkBytes = result.toByteArray();

            screenshotMarks.add(new ScreenshotMark(name, screenshotMarkBytes, (long) screenshotMarkBytes.length));

        } catch (IOException e) {
            System.err.println(name + " "+e.getMessage());
        }
    }
    if (consumer != null) consumer.accept(Optional.of(screenshotMarks));
}

ScreenshotMark 对象


public class ScreenshotMark {
    private String name;
    private byte[] bytes;
    private Long size;

    public ScreenshotMark() {
    }

    public ScreenshotMark(String name, byte[] bytes, Long size) {
        this.name = name;
        this.bytes = bytes;
        this.size = size;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public byte[] getBytes() {
        return bytes;
    }

    public void setBytes(byte[] bytes) {
        this.bytes = bytes;
    }

    public Long getSize() {
        return size;
    }

    public void setSize(Long size) {
        this.size = size;
    }
}

测试

public static void main(String[] args) throws IOException {
    // 设置驱动路径环境变量
    // 必须是全路径
    System.setProperty("webdriver.chrome.driver", "<path>/chromedriver.exe");
    WebDriver webDriver = null;
    try {
        ChromeOptions options = initWebOption("");
        webDriver = new ChromeDriver(options);

        webDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
        webDriver.manage().window().maximize();
        webDriver.get("https://www.baidu.com");
        long timeout = 15000;
        webDriver.manage().window().setSize(new Dimension(1920, 1080));
        webDriver.manage().timeouts().pageLoadTimeout(timeout, TimeUnit.MILLISECONDS);
        webDriver.manage().timeouts().setScriptTimeout(timeout, TimeUnit.MILLISECONDS);
        webDriver.manage().timeouts().implicitlyWait(timeout, TimeUnit.MILLISECONDS);
        System.out.println("打开驱动实例成功...");


        // 获取所有的a标签,并标记
        List<WebElement> webElements = webDriver.findElements(By.xpath("//a"));

        ScreenshotServiceImpl screenshotService = new ScreenshotServiceImpl();
        screenshotService.screenshotAndMark(webDriver, webElements, WebElement::getText, optional -> {
            // 该处为了演示,实际可以拿到bytes[] 上传到服务器,或其他地方
            if (optional.isPresent()) {

                for (ScreenshotMark screenshotMark : optional.get()) {
                    String name = screenshotMark.getName();
                    name = name == null || name.length() == 0 ? Math.random() + "" : name;
                    try (ByteArrayInputStream bytes = new ByteArrayInputStream(screenshotMark.getBytes())) {
                        BufferedImage image = ImageIO.read(bytes);
                        ImageIO.write(image, "png", new File("<path>/" + name + ".png"));
                    } catch (IOException e) {
                        System.err.println("error finally:" + e.getMessage());
                    }
                }
            }
        });

    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        System.out.println("completed finally.");
        if (webDriver != null) webDriver.quit();
    }
}

备注


作者: 小浪
版权声明: 本博客所有文章除特別声明外, 均采用 CC BY 4.0 许可协议。转载请注明来源 小浪!
  目录