Selenium ChromeDriver 截圖標記指定元素的方法

selenium.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();
    }
}

備註

原文鏈接 IT浪子の博客 > Selenium ChromeDriver 截圖標記指定元素的方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章