Java/Kotlin selenium 无头浏览器 [Headless Chrome] 实现长截图 三种方式

在自动化测试和网页抓取中,完整捕获整个页面内容是常见需求。传统截图只能捕获当前视窗内容,无法获取超出可视区域的页面部分。长截图技术通过截取整个滚动页面解决了这个问题,特别适用于:

  1. 保存完整网页存档
  2. 生成页面可视化报告
  3. 验证响应式设计
  4. 捕获动态加载内容

本文将深入探讨三种Java/Kotlin Selenium实现长截图的专业方案,使用无头Chrome浏览器作为运行环境。


一、CDP协议截图(推荐方案)

原理与技术优势

Chrome DevTools Protocol(CDP)是Chrome提供的底层调试协议,通过Page.captureScreenshot命令可直接获取整个页面渲染结果,包括:

  • 超出视口的滚动区域
  • 固定定位元素
  • CSS动画状态

核心优势

  • 原生浏览器支持,无需调整窗口大小
  • 性能最佳(约比传统方法快3-5倍)
  • 支持视网膜屏高分辨率截图

完整实现代码

public class CdpScreenshotter {public static String captureFullPageScreenshot(WebDriver driver) {// 1. 匹配CDP版本Optional<CdpVersion> version = new CdpVersionFinder().match(driver.getCapabilities().getBrowserVersion());if (!version.isPresent()) {throw new RuntimeException("未找到匹配的CDP版本,请检查浏览器版本");}// 2. 配置截图参数Map<String, Object> params = new HashMap<>();params.put("format", "png");params.put("quality", 90); // 图片质量 (0-100)params.put("captureBeyondViewport", true); // 关键参数:捕获超出视口的内容params.put("fromSurface", true); // 捕获合成后的表面// 3. 执行CDP命令@SuppressWarnings("unchecked")Map<String, String> response = (Map<String, String>) ((HasCdp) driver).executeCdpCommand("Page.captureScreenshot", params);// 4. 提取并处理base64数据return response.get("data");}public static void saveScreenshot(String base64Data, String filePath) {byte[] imageBytes = Base64.getDecoder().decode(base64Data.replaceFirst("^data:image/\\w+;base64,", ""));try (FileOutputStream stream = new FileOutputStream(filePath)) {stream.write(imageBytes);} catch (IOException e) {throw new RuntimeException("截图保存失败", e);}}
}

Kotlin实现版本

object CdpScreenshotter {fun captureFullPageScreenshot(driver: WebDriver): String {val version = CdpVersionFinder().match(driver.capabilities.getBrowserVersion())?: throw RuntimeException("未找到匹配的CDP版本")val params = mutableMapOf<String, Any>("format" to "png","quality" to 90,"captureBeyondViewport" to true,"fromSurface" to true)val response = (driver as HasCdp).executeCdpCommand("Page.captureScreenshot", params) as Map<String, String>return response["data"]!!}fun saveScreenshot(base64Data: String, filePath: String) {val cleanData = base64Data.replace(Regex("^data:image/\\w+;base64,"), "")val imageBytes = Base64.getDecoder().decode(cleanData)File(filePath).writeBytes(imageBytes)}
}

最佳实践建议

  1. 版本兼容性处理:定期更新cdpVersionFinder库,确保支持新版Chrome
  2. 内存优化:处理大页面时使用流式写入避免OOM
  3. 错误处理:添加重试机制应对网络波动
  4. 性能监控:记录命令执行时间优化测试套件

二、浏览器窗口调整方案

实现原理与适用场景

通过JavaScript获取页面完整尺寸,然后调整浏览器窗口大小至整个页面尺寸,最后执行传统截图。

适用场景

  • 不支持CDP的老版本浏览器
  • 需要兼容多浏览器引擎(Firefox, Safari等)
  • 简单页面快速实现

增强版实现(解决常见问题)

public class WindowResizeScreenshotter {public static <T> T captureFullPage(TakesScreenshot instance, OutputType<T> outputType) {WebDriver driver = extractDriver(instance);// 保存原始窗口状态Dimension originalSize = driver.manage().window().getSize();Point originalPosition = driver.manage().window().getPosition();try {// 计算页面完整尺寸Dimension pageSize = calculateFullPageSize(driver);// 特殊处理:应对最小窗口限制Dimension adjustedSize = ensureMinimumSize(pageSize);// 调整窗口driver.manage().window().setSize(adjustedSize);// 等待页面重排完成waitForPageSettled(driver);// 执行截图return instance.getScreenshotAs(outputType);} finally {// 恢复原始状态driver.manage().window().setPosition(originalPosition);driver.manage().window().setSize(originalSize);}}private static Dimension calculateFullPageSize(WebDriver driver) {JavascriptExecutor js = (JavascriptExecutor) driver;// 获取包含视口和滚动区域的完整尺寸long fullHeight = (Long) js.executeScript("return Math.max(" +"document.documentElement.scrollHeight, " +"document.body.scrollHeight, " +"document.documentElement.clientHeight" +");");long fullWidth = (Long) js.executeScript("return Math.max(" +"document.documentElement.scrollWidth, " +"document.body.scrollWidth, " +"document.documentElement.clientWidth" +");");return new Dimension((int) fullWidth, (int) fullHeight);}private static Dimension ensureMinimumSize(Dimension size) {// 确保尺寸不小于浏览器允许的最小值int minWidth = Math.max(size.width, 100);int minHeight = Math.max(size.height, 100);return new Dimension(minWidth, minHeight);}private static void waitForPageSettled(WebDriver driver) {new WebDriverWait(driver, Duration.ofSeconds(5)).ignoring(StaleElementReferenceException.class).until(d -> {Object result = ((JavascriptExecutor) d).executeScript("return document.readyState");return "complete".equals(result);});}
}

注意事项

  1. 无头模式必须:确保使用Headless Chrome避免可见窗口限制
    ChromeOptions options = new ChromeOptions();
    options.addArguments("--headless=new"); // Chrome 109+推荐语法
    options.addArguments("--window-size=1920,1080");
    
  2. 页面重排问题:调整大小后等待页面稳定
  3. 内存限制:超大页面可能导致浏览器崩溃
  4. 固定定位元素:可能被错误截断

三、AShot高级截图库方案

框架优势与专业功能

AShot是专为Selenium设计的高级截图库,提供:

  • 智能视口拼接算法
  • 设备像素比(DPR)支持
  • 元素级截图能力
  • 阴影DOM处理

专业级实现(含DPR处理)

public class AShotScreenshotter {public static BufferedImage captureFullPage(WebDriver driver) {// 获取设备像素比float dpr = getDevicePixelRatio(driver);// 配置专业级截图策略ShootingStrategy strategy = ShootingStrategies.viewportRetina(new WebDriverCoordsProvider(),new HorizontalScrollDecorator(),new VerticalScrollDecorator(),dpr).setScrollTimeout(1000);return new AShot().shootingStrategy(strategy).addIgnoredAreas(calculateIgnoredAreas(driver)) // 忽略动态广告区域.takeScreenshot(driver).getImage();}private static float getDevicePixelRatio(WebDriver driver) {try {Object result = ((JavascriptExecutor) driver).executeScript("return window.devicePixelRatio || 1;");return Float.parseFloat(result.toString());} catch (Exception e) {return 1.0f;}}private static Collection<Coords> calculateIgnoredAreas(WebDriver driver) {// 示例:忽略已知广告区域List<WebElement> ads = driver.findElements(By.cssSelector(".ad-container"));return ads.stream().map(e -> {Point location = e.getLocation();Dimension size = e.getSize();return new Coords(location.x, location.y, size.width, size.height);}).collect(Collectors.toList());}public static void saveImage(BufferedImage image, String path) {try {ImageIO.write(image, "PNG", new File(path));} catch (IOException e) {throw new RuntimeException("图片保存失败", e);}}
}

高级功能配置

// 创建自定义截图策略
ShootingStrategy advancedStrategy = new ShootingStrategy() {@Overridepublic BufferedImage getScreenshot(WebDriver driver) {// 自定义截图逻辑}@Overridepublic BufferedImage getScreenshot(WebDriver driver, WebElement element) {// 元素级截图}
};// 配置复杂截图参数
AShot aShot = new AShot().withDpr(2.0f) // 明确设置设备像素比.imageCropper(new IndentCropper(10)) // 添加10像素边框.coordsProvider(new SmartCoordsProvider()) // 智能坐标检测.screenshotDecorator(new BlurDecorator(5)); // 添加模糊效果

疑难问题解决方案

1. 截图出现空白区域

原因:页面包含懒加载内容
解决方案

// 滚动页面触发加载
js.executeScript("window.scrollTo(0, document.body.scrollHeight)");
Thread.sleep(1000); // 等待内容加载

2. CDP版本不匹配

解决方案:自动版本探测

public String findCompatibleCdpVersion(String browserVersion) {List<String> versions = Arrays.asList("115", "114", "113");for (String v : versions) {if (browserVersion.startsWith(v)) return v;}return "latest";
}

3. 超大页面内存溢出

优化策略

// 分块截图并合并
List<BufferedImage> segments = new ArrayList<>();
int segmentHeight = 5000; // 5,000像素分段for (int y = 0; y < totalHeight; y += segmentHeight) {js.executeScript("window.scrollTo(0, " + y + ")");BufferedImage segment = // 截取当前视口segments.add(segment);
}// 使用ImageIO合并图像

结论

  1. 现代浏览器优先选择CDP方案:性能最佳,实现简单
  2. 兼容性要求选择窗口调整:适合跨浏览器测试
  3. 复杂页面使用AShot:处理特殊布局和元素
  4. 无头模式需要的配置
    ChromeOptions options = new ChromeOptions();
    options.addArguments("--headless=new");
    options.addArguments("--disable-gpu");
    options.addArguments("--no-sandbox");
    
简单页面
兼容性需求
复杂动态页面
开始截图
页面类型
使用CDP方案
使用窗口调整方案
使用AShot方案

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/pingmian/84961.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【AI大模型】Elasticsearch9 + 通义大模型实现语义检索操作详解

目录 一、前言 二、Elasticsearch9 语义检索介绍 2.1 ES9 语义检索核心特性 2.2 semantic_text 字段类型说明 2.3 ES9 语义检索原理 2.4 ES9 语义检索优势与使用场景 三、 Elasticsearch9 搭建过程 3.1 环境说明 3.2 部署方式一 3.2.1 创建docker网络 3.2.2 获取es9镜…

linux开机原理以及如何开关机-linux023

linux开机原理以及如何开关机 Linux 系统启动过程概述 阶段描述内核引导启动时&#xff0c;BIOS执行自检&#xff0c;启动设备通常是硬盘。操作系统接管硬件后&#xff0c;读取/boot目录下的内核文件。运行 initinit是系统所有进程的起点&#xff0c;负责启动其他进程。它读取…

使用 socat 和 xinetd 将程序绑定到端口运行

在现代网络应用开发和系统管理中&#xff0c;经常需要将某些程序或脚本绑定到特定的网络端口上&#xff0c;以实现远程访问或服务化。例如&#xff0c;一个简单的 Python 脚本可能需要通过 TCP 端口提供服务&#xff0c;或者一个命令行工具需要通过网络接口暴露其功能。为了实现…

电阻篇---上拉电阻

一、上拉电阻的定义与本质 定义&#xff1a;上拉电阻是一端连接到电源&#xff08;VCC&#xff09;&#xff0c;另一端连接到电路节点的电阻元件&#xff0c;其核心作用是将该节点的电平 “拉” 至电源电压&#xff0c;使其在无信号输入时保持稳定的高电平状态。 本质原理&…

前端持续集成和持续部署简介

持续集成&#xff08;CI&#xff09;&#xff1a;代码提交后自动触发构建、静态检查、单元测试&#xff0c;确保代码质量。 持续部署&#xff08;CD&#xff09;&#xff1a;通过流水线将测试通过的代码自动发布到测试/生产环境&#xff0c;减少人工操作失误。 CI/CD 工具链 …

Elasticsearch高效文章搜索实践

功能 创建索引和映射 使用postman添加映射和查询 查询所有的文章信息&#xff0c;批量导入到es索引库中 server:port: 9999 spring:application:name: es-articledatasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/leadnews_article?useU…

React 中除了react-router还有哪些路由方案

在用React开发时&#xff0c;常用的路由是react-router &#xff0c;但除此之外&#xff0c;还有两个路由方案&#xff0c;因为他们具备 react-router 没有的特性。 1. tanstack/router 1.1. 主要特性 100% 推断的 TypeScript 支持 类型安全的导航 嵌套路由和布局路由 内置…

VINS-Fusion 简介、安装、编译、数据集/相机实测

目录 VINS-Fusion 简介 安装 VINS-Fusion 源码安装 运行数据集 双目模式 单目IMU 模式 双目IMU 模式 D455 相机实际运行 双目IMU 模式 VINS-Fusion 简介 VINS-Fusion 是继 VINS-Mono 和 VINS-Mobile&#xff08;单目视觉惯导 SLAM 方案&#xff09;后&#xff0c;香港科 技大学…

SQL Developer 表复制

SQL Developer 表复制 此方法在数据量比较大时&#xff0c;比一条一条的insert要快得多&#xff1b;具体是会覆盖掉原数据&#xff0c;还是增量的处理&#xff0c;请自行创建demo表测试一下。 注意&#xff1a;原库版本要与目标库数据库版本一致&#xff0c;否则可能会报错的。…

影视剧学经典系列-梁祝-《吕氏春秋·应同》

1、背景 07版电视剧《梁山伯与祝英台》中&#xff0c;谢道韫作为先生&#xff0c;给学生讲了其中的句子。 2、名言 君为尊&#xff0c;以白为黑&#xff0c;臣不能从&#xff1b;父虽亲&#xff0c;以黑为白&#xff0c;子不能从”出自《吕氏春秋应同》 其意为&#xff0c;…

异步爬虫---

代码结构分析 这是一个同步新闻爬虫程序&#xff0c;主要包含以下几个部分&#xff1a; 们把爬虫设计为一个类&#xff0c;类在初始化时&#xff0c;连接数据库&#xff0c;初始化logger&#xff0c;创建网址池&#xff0c;加载hubs并设置到网址池。 爬虫开始运行的入口就是r…

微服务架构中的 Kafka:异步通信与服务解耦(二)

三、Kafka 基础入门 3.1 Kafka 是什么 Kafka 最初由 LinkedIn 公司开发&#xff0c;是一个开源的分布式事件流平台&#xff0c;后成为 Apache 基金会的顶级项目 。它不仅仅是一个简单的消息队列&#xff0c;更是一个分布式流处理平台&#xff0c;具备强大的消息队列、存储系统…

Lighthouse与首屏优化

之前提到首屏优化&#xff0c;想到的就是Vue项目首页打开很慢需要优化。一般都是肉眼看看&#xff0c;对当前的加载速度并没有一个准确的衡量标准&#xff0c;也没有很清晰的解决思路。 前两天我想给自己的网站申请谷歌广告&#xff0c;听说审核对网站的性能要求很高。于是网上…

Maven 之 打包项目时没有使用本地仓库依赖问题

背景 pom 中使用了第三方jar包&#xff0c;远程仓库设置的是阿里云&#xff0c;之前运行很好&#xff0c;今天不知道怎么的&#xff0c;打包总是报错&#xff0c;阿里云仓库无法找到依赖包(本来也没有)&#xff0c;按理来说&#xff0c;编译打包时会优先选择本地仓库的包才对&a…

Mysql基础入门\期末速成

DDL 操作数据库语句 创建&删除数据库语句 创建数据库 create database 数据库名称; -- 直接创建 create database if not exists 数据库名称; -- 如果不存在&#xff0c;则创建 create database 数据库名称 default charset utf8mb4; -- 创建编译类型utf8的数据类型 cre…

SCADA|KingSCADA4.0中历史趋势控件与之前版本的差异

哈喽,你好啊,我是雷工! 最近用到KingSCADA4.0信创版本,也算尝鲜使用。 在使用的过程中发现有些功能或多或少存在一些差异, 这里将遇到的一些不同总结一下,便于后期更好的使用。 01 历史趋势控件 在KingSCADA中有一个历史趋势曲线控件KSHTrend。 该控件既可以连接King…

ubuntu 拒绝ssh连接,连不上ssh,无法远程登录: Connection failed.

目录 问题描述视窗 可视化桌面命令行 问题描述 [C:\~]$ Connecting to 192.166.8.85:22... Could not connect to 192.166.8.85 (port 22): Connection failed.Type help to learn how to use Xshell prompt. [C:\~]$ Connecting to 192.166.8.85:22... Could not connect to …

【大模型应用开发】向量数据库向量检索方法存在问题及优化

一、检索结果重复 1. 问题分析 在构建向量数据库时&#xff0c;对文档分割会存在重复块&#xff08;chunk_overlap&#xff1a;指两个块之间共享的字符数量&#xff0c;用于保持上下文的连贯性&#xff0c;避免分割丢失上下文信息&#xff09;&#xff0c;如下图所示&#xf…

MySQL常用函数详解之数值函数

MySQL常用函数详解之数值函数 一、数值函数概述1.1 数值函数的作用1.2 数值函数分类 二、算术运算函数2.1 加法运算&#xff08;&#xff09;2.2 减法运算&#xff08;-&#xff09;2.3 乘法运算&#xff08;*&#xff09;2.4 除法运算&#xff08;/ 或 DIV&#xff09;2.5 取模…

13、Redis进阶二之Redis数据安全性分析

⼀ 、Redis性能压测脚本介绍 Redis的所有数据是保存在内存当中的&#xff0c; 得益于内存⾼效的读写性能&#xff0c; Redis的性能是⾮常强悍的 。但 是&#xff0c;内存的缺点是断电即丢失&#xff0c;所以 &#xff0c;在实际项⽬中&#xff0c; Redis—旦需要保存—些重要的…