【Selenium】使用ChromeDriver实现页面快照截图

效果图

以下为编写流程,全代码+注释在文章末尾
在这里插入图片描述


下载插件

1. 下载Chrome浏览器版本和ChromeDriver

谷歌版本号114以上版本chrome、chromedriver、chrome-headless-shell下载地址:https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json

本文章使用Chrome、ChromeDriver版本 126.0.6478.182

2. Maven配置依赖

<dependencies>
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>4.0.0</version>
    </dependency>

    <!-- Maven dependency for Commons IO -->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.8.0</version>
    </dependency>

    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>27.0-jre</version>
    </dependency>
</dependencies>

Java代码

3. 设置属性/配置

注意1:设置Chrome和ChromeDriver路径时,我直接将两文件放在项目根目录下得出此路径。如放在其他路径下需写全路径/相对路径。

例如 E:\SG_Company\Selenium1\chromedriver-win64\chromedriver.exe

//浏览器的安装后位置,如果是默认安装路径,这句话可以省略
System.setProperty("webdriver.chrome.bin","chrome-win64/chrome.exe");
//设置下载的驱动路径,Windows对应chromedriver.exe Linux对应chromedriver,具体路径看你把驱动放在哪
System.setProperty("webdriver.chrome.driver","chrome-win64/chromedriver.exe");

//设置Chrome参数对象
ChromeOptions options = new ChromeOptions();
//ssl证书支持
options.setCapability("acceptSslCerts", true);
//截屏支持
options.setCapability("takesScreenshot", true);
//css搜索支持
options.setCapability("cssSelectorsEnabled", true);
//不打开真实浏览器而使用虚拟浏览器执行操作  =  options.addArguments("headless");
options.setHeadless(true);
//me : 禁用沙箱模式,Chrome在没有真正的权限的情况下也可以运行 = options.addArguments("no-sandbox");
options.setCapability("sandbox",false);

//设置浏览器参数
//允许所有请求
options.addArguments("--remote-allow-origins=*");
//设置Webdriver启动chrome为默认用户的配置信息(包括书签、扩展程序等),否则将以默认空白配置启动Chrome
//如果需要ChromeDriver执行插件,则需要加上配置信息
options.addArguments("user-data-dir=C:/Users/xxxx/AppData/Local/Google/Chrome for Testing/User Data");
//禁用信息栏,信息栏通常用于显示非阻塞的通知和提示信息
options.addArguments("disable-infobars");
//禁用浏览器中的弹出窗口拦截功能。这个参数允许Selenium脚本在执行过程中不会因为浏览器拦截弹出窗口而导致测试失败或脚本执行异常,通过禁用弹出窗口拦截,可以确保测试脚本能够正常地处理那些需要用户手动允许的弹出窗口,从而保证测试的顺利进行
options.addArguments("--disable-popup-blocking");
//禁用GPU加速可以减少资源消耗,但是整体上来说禁不禁用都可以 
//options.addArguments("--disable-gpu");

//打开ChromeDriver
ChromeDriver driver = new ChromeDriver(options);
//设置超时,避免有些内容加载过慢导致截不到图
//指定页面加载的超时时间。定义WebDriver等待页面完全加载完成的最长时间为30秒,如果在这个时间内页面没有完全加载完成,WebDriver将会抛出一个超时错误
driver.manage().timeouts().pageLoadTimeout(30, TimeUnit.SECONDS);
//设置全局隐式等待时间。WebDriver在查找页面元素时,如果元素未立即出现,WebDriver将等待指定的时间以寻找该元素。
//如果这30秒内元素被找到,那么WebDriver将继续执行后续的操作;如果30秒后元素仍未出现,那么WebDriver将抛出NoSuchElementException异常。
//隐式等待是全局设置的,意味着它会影响到WebDriver实例中执行的所有元素查找操作。
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
//脚本执行超时,设置JavaScript执行超时时间。具体来说,这个方法设置了WebDriver执行JavaScript脚本的最大等待时间
driver.manage().timeouts().setScriptTimeout(30, TimeUnit.SECONDS);

4. 访问登录地址,判断cookie是否有效,有效直接读,无效写入新的cookie存储并返回

注意1:这里checkCookieValied是我自己尝试判断是否过期,直接拿时间对比。实际运用中Cookie基本被加密无法破解,也可以直接调用公司的 “判断是否有效” 方法

注意2:此处redis写得不够详细,具体存储位置可根据个人选择编写

static private final String LoginURL = "https://xxxx/xxx/public/login/login.html";

driver.get(LoginURL);
( checkCookieValied() ? readCookieFile() : writeCookieFile(driver) ).forEach( (c) -> driver.manage().addCookie(c));

/**
  * 判断 Cookie没有过期,如果过期拿新的 Cookie并存储
  * @return True没过期  False过期了
  */
public boolean checkCookieValied(){
    //获取redis中的cookie 这里方便测试我就直接创建新cookie对象了
    //Set<Cookie> list = redisTemplate.getCookies();
    Cookie cookie = new Cookie("","");
    //redis中不存在对应cookie 或者 redis中cookie时间小于当前时间   视为过期False
    //判断过期的接口也可以由对象给,直接调用
    if(cookie == null || cookie.getExpiry().before(new Date())){
        return false;
    }
    //redis中cookie未过期 True
    return true;
}

/**
  * 从redis中读取cookie?
  * @return Set<Cookie>
  */
public Set<Cookie> readCookieFile(){
    //从redis获取cookie ,通常调用方法即可 (方便测试直接创建对象)
    //Set<Cookie> list = redisTemplate.getCookies();
    Set<Cookie> list = new HashSet<>();
    //设置过期时间,根据业务确定
    Date expire = new Date(new Date().getTime() + 60 * 1000 * 15);
    //获取当前cookie添加到集合,有的cookie需要加密,根据业务需求拿取
    //参数对应key value domain path expire isSecure HttpOnly
    Cookie JSESSIONID = new Cookie("AAAA", "FB5E985969297BC7C7xxxxxxxxxxx", "your cookies domain", "path", expire, true, true);
    list.add(JSESSIONID);
    Cookie route = new Cookie("BBBB", "43004bc2b469a98e031cxxxxxxxxxxxx", "your cookies domain", "path", expire);
    list.add(route);

    return list;
}

/**
  * 将新的cookie写入redis,
  * @return Set<Cookie>
  */
public Set<Cookie> writeCookieFile(ChromeDriver driver){
    //删除原有所有的Cookie
    driver.manage().deleteAllCookies();

    //重新打开登录页面登录,获取cookie 
    driver.get(LoginURL);
    // 查找到输入框,在搜索输入框中输入用户名 密码 并点击Login
    driver.findElement(By.id("user")).sendKeys(USERNAME);
    driver.findElement(By.id("password")).sendKeys(PASSWORK);
    driver.findElement(By.id("login-btn-account")).click();
    //登录成功,获取当前cookie
    Set<Cookie> cookies = driver.manage().getCookies();
    //写入到redis存储地址(根据业务确定)
    //redisTeplate.saveCookies(cookies);

    return cookies;
}

5. 访问需截图的真实地址(无需登录则可直接访问真实地址),取得所需宽高

注意1: 如果页面大小始终固定,不存在需要模拟滑动,则功能可删

注意2: 有的页面内容显示内容随着窗口大小变化而等比例变化,始终不会出现滚动条。这种在扩大窗口后,可能有保持原比例不变的情况,例如:截图发现小块内容大块空白。 解决方式:有的页面需要在跳转页面后执行 “ 某操作 ” 才能适应新窗口,需要写某操作,这里直接和模拟滑动放一块了。

//刷新页面,跳转目的Url
driver.navigate().refresh();
driver.get(url);

//等待加载
Thread.sleep(1000);

//获取高度和宽度一定要在设置URL之后,不然会导致获取不到页面真实的宽高;
//scrollWidth:返回元素整体的宽度,包括由于 overflow 溢出而在屏幕上不可见的内容。
Long width = (Long)driver.executeScript("return document.documentElement.scrollWidth");
Long height =(Long) driver.executeScript("return document.documentElement.scrollHeight");
//这里需要模拟滑动,有些是滑动的时候才加载的(懒加载)
long temp_height = 0;
while (true) {
    //每次滚动500个像素,懒加载所以每次等待100毫秒 具体时间可以根据具体业务场景去设置
    Thread.sleep(100);
    //window.scrollBy(0,500) 向下滚动500个像素   window.scrollBy(500,0) 向右滑动500个像素  window.scrollTo(x,y) 滑动到具体位置
    driver.executeScript("window.scrollBy(0,500)");
    temp_height += 500;
    //到底退出
    if(temp_height>=height){
        break;
    }
}

6. 若截取页面某元素,获取元素

注意1:截取元素 是 从当前的大图中扣取出来,所以要先拿到当前大图

注意2:此处设置的宽高不可超过系统窗口大小,否则会报错
BufferedImage img = image.getSubimage(point.getX(), point.getY(), width - point.getX(), height - point.getY());

注意3:有的页面可能出现 “创建了新窗口” 情况,需要switchTo到新窗口,否则会出现无法截图 或 无法找到元素的Bug

//设置窗口大小:获取高度和宽度一定要在设置URL之后,不然会导致获取不到页面真实的宽高;
driver.manage().window().setSize(new Dimension(WindowWidth, WindowHeight));

//根据Class获取元素
//用户可以传一个xPath过来,用于选中元素,没传默认截全图
WebElement element = driver.findElement(By.xpath(xPath == null || xPath.isBlank() ? "/html/body/div" : xPath));

//获取元素大小固定时,从全屏截图里扣出来,设定长宽为 元素长宽
if(!eleChange){
    //如果后续还有其他需求可以创建一个矩形Rectangle 对矩形进行操作
    width = element.getSize().getWidth();
    height = element.getSize().getHeight();
    //创建图片截图
    BufferedImage image = ImageIO.read(srcFile);
    //元素坐标,.getX() .getY()指定矩形区域左上角的  X坐标  Y坐标
    Point point = element.getLocation();
    //返回由指定矩形区域定义的子图像BufferedImage。返回的BufferedImage与原始图像共享相同的数据数组
    BufferedImage img = image.getSubimage(point.getX(), point.getY(), width - point.getX(), height - point.getY());
    //ImageIO.write()使用支持给定格式的任意ImageWriter将图像写入File
    //img:reader "png"-formatName:包含格式的非正式名称的字符串  srcFile: output要写入的文件
    ImageIO.write(img, "png", srcFile);
}

7. File截图保存,关闭浏览器

注意:关闭浏览器最好写在try catch finally 中,无论是否出错都关闭

//完整截图
File srcFile = driver.getScreenshotAs(OutputType.FILE);
//设置截图文件保存的路径,拷贝文件到文件
FileUtils.copyFile(srcFile, new File(savePath));
//关闭浏览器
driver.quit();

附Chrom参数配置

1 --allow-outdated-plugins 不停用过期的插件。
2 --allow-running-insecure-content 默认情况下,https 页面不允许从 http 链接引用 javascript/css/plug-ins。添加这一参数会放行这些内容。
3 --allow-scripting-gallery 允许拓展脚本在官方应用中心生效。默认情况下,出于安全因素考虑这些脚本都会被阻止。
4 --disable-accelerated-video 停用 GPU 加速视频。
5 --disable-dart 停用 Dart。
6 --disable-desktop-notifications 禁用桌面通知,在 Windows 中桌面通知默认是启用的。
7 --disable-extensions 禁用拓展。
8 --disable-file-system 停用 FileSystem API。
9 --disable-preconnect 停用 TCP/IP 预连接。
10 --disable-remote-fonts 关闭远程字体支持。SVG 中字体不受此参数影响。
11 --disable-speech-input 停用语音输入。
12 --disable-web-security 不遵守同源策略。
13 --disk-cache-dir 将缓存设置在给定的路径。
14 --disk-cache-size 设置缓存大小上限,以字节为单位。
15 --dns-prefetch-disable 停用DNS预读。
16 --enable-print-preview 启用打印预览。
17 --extensions-update-frequency 设定拓展自动更新频率,以秒为单位。
18 --incognito 让浏览器直接以隐身模式启动。
19 --keep-alive-for-test 最后一个标签关闭后仍保持浏览器进程。(某种意义上可以提高热启动速度,不过你最好得有充足的内存)
20 --kiosk 启用kiosk模式。(一种类似于全屏的浏览模式)
21 --lang 使用指定的语言。
22 --no-displaying-insecure-content 默认情况下,https 页面允许从 http 链接引用图片/字体/框架。添加这一参数会阻止这些内容。
23 --no-first-run 跳过 Chromium 首次运行检查。
24 --no-referrers 不发送 Http-Referer 头。
25 --no-sandbox 彻底停用沙箱。
26 --no-startup-window 启动时不建立窗口。
27 --proxy-pac-url 使用给定 URL 的 pac 代理脚本。(也可以使用本地文件,如 --proxy-pac-url=“file:\c:\proxy.pac”)
28 --proxy-server 使用给定的代理服务器,这个参数只对 http 和 https 有效。(例如 --proxy-server=127.0.0.1:8087 )
29 --single-process 以单进程模式运行 Chromium。(启动时浏览器会给出不安全警告)
30 --start-maximized 启动时浏览器窗口最大化。
31 --user-agent 使用给定的 User-Agent 字符串

32 –user-data-dir=UserDataDir 自订使用者帐户资料夹(如:–user-data-dir=“D:\temp\Chrome User Data”)
33 –process-per-tab 每个分页使用单独进程
34 –process-per-site 每个站点使用单独进程
35 –in-process-plugins 插件不启用单独进程

36 –disable-popup-blocking 禁用弹出拦截
37 –disable-javascript 禁用JavaScript
38 –disable-java 禁用Java
39 –disable-plugins 禁用插件
40 –disable-images 禁用图像
41 –omnibox-popup-count=”num” 将网址列弹出的提示选单数量改为num个
42 –enable-vertical-tabs 调整chrome游览器标签存放在左边,非顶部


全代码(长图+元素)

package org.example;

import org.apache.commons.io.FileUtils;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

public class WebPageScreenshot {
    
    static private final String USERNAME = "username";
    static private final String PASSWORK = "password!";

    //driver的分辨率,这里设置2550*1400
    static private final int WindowWidth = 2550;
    static private final int WindowHeight = 1400;
    
    static private final String LoginURL = "https://xxxx/xxx/public/login/login.html";

    public static void main(String[] args) {
        //访问网址路径
        String url = "https://www.csdn.net";
        //储存文件路径
        String savePath = "screenshotTest.png";
        //元素是否可变(截元素false,截整图长图true)
        String eleChange = true;
        
        google(url,savePath);
    }

    //真实访问路径,图片保存地址,元素是否可变
    public static void google(String url,String savePath,Boolean eleChange){
        //浏览器的安装后位置,如果是默认安装路径,这句话可以省略
        System.setProperty("webdriver.chrome.bin","chrome-win64/chrome.exe");
        //这里设置下载的驱动路径,Windows对应chromedriver.exe Linux对应chromedriver,具体路径看你把驱动放在哪
        System.setProperty("webdriver.chrome.driver","chrome-win64/chromedriver.exe");
        //设置Chrome参数对象
        ChromeOptions options = new ChromeOptions();

        //ssl证书支持
        options.setCapability("acceptSslCerts", true);
        //截屏支持
        options.setCapability("takesScreenshot", true);
        //css搜索支持
        options.setCapability("cssSelectorsEnabled", true);
        //不打开真实浏览器而使用虚拟浏览器执行操作  =  options.addArguments("headless");
        options.setHeadless(true);
        //me : 禁用沙箱模式,Chrome在没有真正的权限的情况下也可以运行   =   options.addArguments("no-sandbox");
        options.setCapability("sandbox",false);

        //设置浏览器参数
//        允许所有请求
        options.addArguments("--remote-allow-origins=*");
//        设置Webdriver启动chrome为默认用户的配置信息(包括书签、扩展程序等)
//        如果需要ChromeDriver执行插件,则需要加上配置信息,否则将以默认空白配置启动Chrome
        options.addArguments("user-data-dir=C:/Users/xxxx/AppData/Local/Google/Chrome for Testing/User Data");
//        禁用信息栏,信息栏通常用于显示非阻塞的通知和提示信息
//        options.addArguments("disable-infobars");
//        禁用浏览器中的弹出窗口拦截功能。这个参数允许Selenium脚本在执行过程中不会因为浏览器拦截弹出窗口而导致测试失败或脚本执行异常,通过禁用弹出窗口拦截,可以确保测试脚本能够正常地处理那些需要用户手动允许的弹出窗口,从而保证测试的顺利进行
//        options.addArguments("--disable-popup-blocking");
//        禁用GPU加速可以减少资源消耗,但是整体上来说禁不禁用都可以 
//        options.addArguments("--disable-gpu");

        //打开ChromeDriver
        ChromeDriver driver = new ChromeDriver(options);
        //设置超时,避免有些内容加载过慢导致截不到图
        //指定页面加载的超时时间。定义WebDriver等待页面完全加载完成的最长时间为30秒,如果在这个时间内页面没有完全加载完成,WebDriver将会抛出一个超时错误
        driver.manage().timeouts().pageLoadTimeout(30, TimeUnit.SECONDS);
        //设置全局隐式等待时间。WebDriver在查找页面元素时,如果元素未立即出现,WebDriver将等待指定的时间以寻找该元素。
        //如果这30秒内元素被找到,那么WebDriver将继续执行后续的操作;如果30秒后元素仍未出现,那么WebDriver将抛出NoSuchElementException异常。
        //隐式等待是全局设置的,意味着它会影响到WebDriver实例中执行的所有元素查找操作。
        driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
        //脚本执行超时,设置JavaScript执行超时时间。具体来说,这个方法设置了WebDriver执行JavaScript脚本的最大等待时间
        driver.manage().timeouts().setScriptTimeout(30, TimeUnit.SECONDS);
        try {
            //(先访问登录页面)
            driver.get(LoginURL);
            
            // 判断cookie是否有效?有效直接读,无效写入新的cookie存储并返回
            ( checkCookieValied() ? readCookieFile() : writeCookieFile(driver) ).forEach( (c) -> driver.manage().addCookie(c));
            
            //刷新页面,跳转目的Url
            driver.navigate().refresh();
            driver.get(url);
            
            //获取高度和宽度一定要在设置URL之后,不然会导致获取不到页面真实的宽高;
            //scrollWidth:返回元素整体的宽度,包括由于 overflow 溢出而在屏幕上不可见的内容。
            Long width = (Long)driver.executeScript("return document.documentElement.scrollWidth");
            Long height =(Long) driver.executeScript("return document.documentElement.scrollHeight");
            //这里需要模拟滑动,有些是滑动的时候才加载的(懒加载)
            long temp_height = 0;
            while (true) {
                //每次滚动500个像素,懒加载所以每次等待2S 具体时间可以根据具体业务场景去设置
                Thread.sleep(1000);
                //执行js脚本 模拟滑动
                driver.executeScript("window.scrollBy(0,500)");
                temp_height += 500;
                //到底退出
                if(temp_height>=height){
                    break;
                }
            }
            //设置窗口宽高,设置后才能截全
            driver.manage().window().setSize(new Dimension(width.intValue(), height.intValue()));
            //完整截图 File流
            File srcFile = driver.getScreenshotAs(OutputType.FILE);
            
            //截取元素
            //根据Class获取元素
            WebElement element = driver.findElement(By.className("className"));

            //获取元素大小固定时,从全屏截图里扣出来,设定长宽为 元素长宽
            if(!eleChange){
                //如果后续还有其他需求可以创建一个矩形Rectangle 对矩形进行操作
                width = element.getSize().getWidth();
                height = element.getSize().getHeight();
                //创建图片截图
                BufferedImage image = ImageIO.read(srcFile);
                //元素坐标,.getX() .getY()指定矩形区域左上角的  X坐标  Y坐标
                Point point = element.getLocation();
                //返回由指定矩形区域定义的子图像BufferedImage。返回的BufferedImage与原始图像共享相同的数据数组
                BufferedImage img = image.getSubimage(point.getX(), point.getY(), width - point.getX(), height - point.getY());
                //使用支持给定格式的任意ImageWriter将图像写入File
                //img一个reader image要写,formatName包含格式的非正式名称的字符串,output要写入的文件
                ImageIO.write(img, "png", srcFile);
            }
            
            //设置截图文件保存的路径,拷贝文件到文件
            FileUtils.copyFile(srcFile, new File(savePath));
        }catch (Exception e){
            throw new RuntimeException("截图失败",e);
        }finally {
            //关闭当前窗口
//        driver.close();
            //!关闭浏览器
            driver.quit();
        }
    }
    
    /**
     * 判断 Cookie没有过期,如果过期拿新的 Cookie并存储
     * @return True没过期  False过期了
     */
    public boolean checkCookieValied(){
        //获取redis中的cookie 这里方便测试我就直接创建新cookie对象了
        Cookie cookie = new Cookie("","");
        //redis中不存在对应cookie 或者 redis中cookie时间小于当前时间   视为过期False
        //判断过期的接口也可以由对象给,直接调用
        if(cookie == null || cookie.getExpiry().before(new Date())){
            return false;
        }
        //redis中cookie未过期 True
        return true;
    }

    /**
     * 从redis中读取cookie?
     * @return List<Cookie>
     */
    public Set<Cookie> readCookieFile(){
        //从redis获取cookie(方便测试 直接创建的对象)
        Set<Cookie> list = new HashSet<>();
        //设置过期时间
        Date expire = new Date(new Date().getTime() + 60 * 1000 * 15);
        //获取当前cookie并添加到集合,有的cookie需要加密,根据业务需求拿取
        //参数对应key value domain path expire isSecure HttpOnly
        Cookie JSESSIONID = new Cookie("AAAA", "FB5E985969297BC7C7xxxxxxxxxxx", "your cookies domain", "path", expire, true, true);
        list.add(JSESSIONID);
        Cookie route = new Cookie("BBBB", "43004bc2b469a98e031cxxxxxxxxxxxx", "your cookies domain", "path", expire);
        list.add(route);

        return list;
    }

    /**
     * 将新的cookie写入redis?
     * @return List<Cookie>
     */
    public Set<Cookie> writeCookieFile(ChromeDriver driver){
        //写入到当前driver前,先删除所有的Cookie后重新添加
        driver.manage().deleteAllCookies();

        //重新登录,获取cookie (这时候会不会自动更新cookie有效时间?)
        // 打开浏览器登录页面
        driver.get(LoginURL);
        // 查找到输入框,在搜索输入框中输入用户名 密码 并点击Login
        driver.findElement(By.id("user")).sendKeys(USERNAME);
        driver.findElement(By.id("password")).sendKeys(PASSWORK);
        driver.findElement(By.id("login-btn-account")).click();
        //登录成功,获取当前cookie
        Set<Cookie> cookies = driver.manage().getCookies();
        //写入到redis存储地址(根据业务确定)
        //redis.save(xxx)

        return cookies;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值