通过Selenium实现网页截图来生成应用封面
一. 什么是Selenium
Selenium 是一个开源的 Web 应用程序自动化测试工具集,主要用于模拟用户在浏览器中的各种操作,实现对 Web 应用的自动化测试、数据爬取、定期任务执行等功能。其核心是 WebDriver,它提供了一套跨编程语言、跨浏览器的 API,让开发者可以通过代码控制浏览器行为(如点击、输入、跳转、截图等)。
Selenium的官方文档:Selenium 浏览器自动化项目 | Selenium
在实际使用建议搭配WebDriverManager使用,可以自动化管理浏览器驱动。解决了开发中的许多痛点:
1. 无需手动下载浏览器驱动,告别找版本、下文件的繁琐;
2. 自动匹配浏览器版本,避免因版本不兼容导致的运行失败;
3. 省去环境变量配置,代码无需硬编码驱动路径,跨机器/系统运行更顺畅;
4. 简化跨环境和团队协作,不用手动同步各环境的驱动版本,降低维护成本。
对比维度 | 不使用 WebDriverManager | 使用 WebDriverManager |
---|---|---|
驱动与版本 | 手动下载,需人工匹配浏览器版本,易因更新报错 | 自动检测浏览器版本,下载匹配驱动,适配更新 |
环境配置 | 需手动配置环境变量或硬编码路径,换环境需重配 | 无需配置,自动管理驱动路径,跨环境通用 |
跨环境 / 团队协作 | 需手动同步所有环境驱动,易出现不一致问题 | 自动适配环境,代码一次编写多环境可用 |
维护与错误风险 | 需人工维护驱动,版本 / 路径问题易导致运行失败 | 全自动化管理,大幅降低维护成本和错误风险 |
二. 开发实现
1. 本地生成截图
1.1 引入Selenium网页截图依赖
<dependency><groupId>org.seleniumhq.selenium</groupId><artifactId>selenium-java</artifactId><version>4.33.0</version>
</dependency>
<dependency><groupId>io.github.bonigarcia</groupId><artifactId>webdrivermanager</artifactId><version>6.1.0</version>
</dependency>
1.2 提供根据URL生成截图文件并返回路径的方法,用于网页截图
1.初始化驱动
@Slf4j
public class WebScreenshotUtils {private static final WebDriver webDriver;static {final int DEFAULT_WIDTH = 1600;final int DEFAULT_HEIGHT = 900;webDriver = initChromeDriver(DEFAULT_WIDTH, DEFAULT_HEIGHT);}@PreDestroypublic void destroy() {webDriver.quit();}/*** 初始化 Chrome 浏览器驱动*/private static WebDriver initChromeDriver(int width, int height) {try {// 自动管理 ChromeDriverWebDriverManager.chromedriver().setup();// 配置 Chrome 选项ChromeOptions options = new ChromeOptions();// 无头模式(Chrome浏览器在后台运行,不会弹出窗口)options.addArguments("--headless");// 禁用GPU(在某些环境下避免问题)options.addArguments("--disable-gpu");// 禁用沙盒模式(Docker环境需要)options.addArguments("--no-sandbox");// 禁用开发者shm使用options.addArguments("--disable-dev-shm-usage");// 设置窗口大小options.addArguments(String.format("--window-size=%d,%d", width, height));// 禁用扩展options.addArguments("--disable-extensions");// 设置用户代理options.addArguments("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");// 创建驱动WebDriver driver = new ChromeDriver(options);// 设置页面加载超时driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(30));// 设置隐式等待driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));return driver;} catch (Exception e) {log.error("初始化 Chrome 浏览器失败", e);throw new BusinessException(ErrorCode.SYSTEM_ERROR, "初始化 Chrome 浏览器失败");}}
}
这是样板代码。
重点:
(1)在静态代码块里初始化驱动,确保整个应用生命周期内只初始化一次
(2)默认使用已经初始化好的驱动实例
(3)在项目停止前正确销毁驱动,释放资源
2. 编写子方法
/*** 保存图片到文件*/
private static void saveImage(byte[] imageBytes, String imagePath) {try {FileUtil.writeBytes(imageBytes, imagePath);} catch (Exception e) {log.error("保存图片失败: {}", imagePath, e);throw new BusinessException(ErrorCode.SYSTEM_ERROR, "保存图片失败");}
}/*** 压缩图片*/
private static void compressImage(String originalImagePath, String compressedImagePath) {// 压缩图片质量(0.1 = 10% 质量)清晰度还行,且减少文件大小final float COMPRESSION_QUALITY = 0.3f;try {ImgUtil.compress(FileUtil.file(originalImagePath),FileUtil.file(compressedImagePath),COMPRESSION_QUALITY);} catch (Exception e) {log.error("压缩图片失败: {} -> {}", originalImagePath, compressedImagePath, e);throw new BusinessException(ErrorCode.SYSTEM_ERROR, "压缩图片失败");}
}/*** 等待页面加载完成*/
private static void waitForPageLoad(WebDriver driver) {try {// 创建等待页面加载对象WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));// 等待 document.readyState 为completewait.until(webDriver ->((JavascriptExecutor) webDriver).executeScript("return document.readyState").equals("complete"));// 额外等待一段时间,确保动态内容加载完成Thread.sleep(2000);log.info("页面加载完成");} catch (Exception e) {log.error("等待页面加载时出现异常,继续执行截图", e);}
}
3. 编写完整的截图方法,思路是访问页面->等待页面加载完成->截图->保存截图文件并压缩->返回压缩后的路径。
/*** 生成网页截图** @param webUrl 网页URL* @return 压缩后的截图文件路径,失败返回null*/
public static String saveWebPageScreenshot(String webUrl) {if (StrUtil.isBlank(webUrl)) {log.error("网页URL不能为空");return null;}try {// 创建临时目录String rootPath = System.getProperty("user.dir") + File.separator + "tmp" + File.separator + "screenshots"+ File.separator + UUID.randomUUID().toString().substring(0, 8);FileUtil.mkdir(rootPath);// 图片后缀final String IMAGE_SUFFIX = ".png";// 原始截图文件路径String imageSavePath = rootPath + File.separator + RandomUtil.randomNumbers(5) + IMAGE_SUFFIX;// 访问网页webDriver.get(webUrl);// 等待页面加载完成waitForPageLoad(webDriver);// 截图byte[] screenshotBytes = ((TakesScreenshot) webDriver).getScreenshotAs(OutputType.BYTES);// 保存原始图片saveImage(screenshotBytes, imageSavePath);log.info("原始截图保存成功: {}", imageSavePath);// 压缩图片final String COMPRESSION_SUFFIX = "_compressed.jpg";String compressedImagePath = rootPath + File.separator + RandomUtil.randomNumbers(5) + COMPRESSION_SUFFIX;compressImage(imageSavePath, compressedImagePath);log.info("压缩图片保存成功: {}", compressedImagePath);// 删除原始图片,只保留压缩图片FileUtil.del(imageSavePath);return compressedImagePath;} catch (Exception e) {log.error("网页截图失败: {}", webUrl, e);return null;}
}
2. 保存截图到对象存储
将生成的封面图上传到腾讯云COS对象存储,使得其能够持久化存储并快速访问。
1.1 在腾讯云控制台中创建一个存储桶:存储桶列表 - 对象存储 - 控制台
1.2 在配置文件中添加COS相关配置
cos:client:host: your-custom-domain.comsecretId: your-secret-idsecretKey: your-secret-keyregion: ap-shanghaibucket: your-bucket-name
1.3 引入依赖
<dependency><groupId>com.qcloud</groupId><artifactId>cos_api</artifactId><version>5.6.227</version>
</dependency>
1.4 创建COS客户端配置类
/*** 腾讯云COS配置类*/
@Configuration
@ConfigurationProperties(prefix = "cos.client")
@Data
public class CosClientConfig {/*** 域名*/private String host;/*** secretId*/private String secretId;/*** 密钥(注意不要泄露)*/private String secretKey;/*** 区域*/private String region;/*** 桶名*/private String bucket;@Beanpublic COSClient cosClient() {// 初始化用户身份信息(secretId, secretKey)COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);// 设置bucket的区域, COS地域的简称请参照 https://www.qcloud.com/document/product/436/6224ClientConfig clientConfig = new ClientConfig(new Region(region));// 生成cos客户端return new COSClient(cred, clientConfig);}
}
1.5 创建可复用的CosManager类,专门负责和COS对象存储进行交互,提供文件上传功能。
/*** COS对象存储管理器*/
@Component
@Slf4j
public class CosManager {@Resourceprivate CosClientConfig cosClientConfig;@Resourceprivate COSClient cosClient;/*** 上传对象** @param key 唯一键* @param file 文件* @return 上传结果*/public PutObjectResult putObject(String key, File file) {PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key, file);return cosClient.putObject(putObjectRequest);}/*** 上传文件到 COS 并返回访问 URL** @param key COS对象键(完整路径)* @param file 要上传的文件* @return 文件的访问URL,失败返回null*/public String uploadFile(String key, File file) {// 上传文件PutObjectResult result = putObject(key, file);if (result != null) {// 构建访问URLString url = String.format("%s%s", cosClientConfig.getHost(), key);log.info("文件上传COS成功: {} -> {}", file.getName(), url);return url;} else {log.error("文件上传COS失败,返回结果为空");return null;}}
}
3. 服务优化
在生成截图时,提供静态方法初始化了一个全局公用的WebDriver来避免重复加载。
优点是性能高,但是在并发截图的场景下,如果共用一个WebDriver,可能会导致截图错误的页面。
// 危险:多线程共享同一个driver
private static final WebDriver webDriver = new ChromeDriver();// 线程A: driver.get("page1.html") -> 截图
// 线程B: driver.get("page2.html") -> 截图
// 结果:线程 A 可能截到 page2 的内容
优化思路:
ThreadLocal 模式,每个线程使用同一个WebDriver:
private static final ThreadLocal<WebDriver> driverThreadLocal = new ThreadLocal<>();public static WebDriver getDriver() {WebDriver driver = driverThreadLocal.get();if (driver == null) {driver = initChromeDriver();driverThreadLocal.set(driver);}return driver;
}
优点是实现简单,能够解决并发问题,缺点是当线程较多时可能会导致内存溢出。
4. 功能优化
定期清理本地临时生成的封面文件,以及在应用删除时关联删除对应的封面图,避免资源浪费。
用Spring Scheduler写一个定时任务:
@Configuration
@EnableScheduling
@Slf4j
public class ScreenshotConfig {/*** 每天凌晨2点清理过期的临时截图文件*/@Scheduled(cron = "0 0 2 * * ?")public void cleanupTempScreenshots() {log.info("开始定时清理过期的临时截图文件");try {WebScreenshotUtils.cleanupTempFiles();log.info("定时清理临时截图文件完成");} catch (Exception e) {log.error("定时清理临时截图文件失败", e);}}
}