一. 什么是Selenium
Selenium 是一个开源的 Web 应用程序自动化测试工具集,主要用于模拟用户在浏览器中的各种操作,实现对 Web 应用的自动化测试、数据爬取、定期任务执行等功能。其核心是 WebDriver,它提供了一套跨编程语言、跨浏览器的 API,让开发者可以通过代码控制浏览器行为(如点击、输入、跳转、截图等)。
Selenium的官方文档:Selenium 浏览器自动化项目 | Selenium
在实际使用建议搭配WebDriverManager使用,可以自动化管理浏览器驱动。解决了开发中的许多痛点:
-
无需手动下载浏览器驱动,告别找版本、下文件的繁琐;
-
自动匹配浏览器版本,避免因版本不兼容导致的运行失败;
-
省去环境变量配置,代码无需硬编码驱动路径,跨机器/系统运行更顺畅;
-
简化跨环境和团队协作,不用手动同步各环境的驱动版本,降低维护成本。
对比维度 | 不使用 WebDriverManager | 使用 WebDriverManager |
---|---|---|
驱动与版本 | 手动下载,需人工匹配浏览器版本,易因更新报错 | 自动检测浏览器版本,下载匹配驱动,适配更新 |
环境配置 | 需手动配置环境变量或硬编码路径,换环境需重配 | 无需配置,自动管理驱动路径,跨环境通用 |
跨环境 / 团队协作 | 需手动同步所有环境驱动,易出现不一致问题 | 自动适配环境,代码一次编写多环境可用 |
维护与错误风险 | 需人工维护驱动,版本 / 路径问题易导致运行失败 | 全自动化管理,大幅降低维护成本和错误风险 |
二. 开发实现
1. 本地生成截图
1.1 引入Selenium网页截图依赖
XML
<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.初始化驱动
java
@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);
}
@PreDestroy
public void destroy() {
webDriver.quit();
}
/**
* 初始化 Chrome 浏览器驱动
*/
private static WebDriver initChromeDriver(int width, int height) {
try {
// 自动管理 ChromeDriver
WebDriverManager.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)在项目停止前正确销毁驱动,释放资源
- 编写子方法
java
/**
* 保存图片到文件
*/
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 为complete
wait.until(webDriver ->
((JavascriptExecutor) webDriver).executeScript("return document.readyState")
.equals("complete")
);
// 额外等待一段时间,确保动态内容加载完成
Thread.sleep(2000);
log.info("页面加载完成");
} catch (Exception e) {
log.error("等待页面加载时出现异常,继续执行截图", e);
}
}
- 编写完整的截图方法,思路是访问页面->等待页面加载完成->截图->保存截图文件并压缩->返回压缩后的路径。
java
/**
* 生成网页截图
*
* @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相关配置
java
cos:
client:
host: your-custom-domain.com
secretId: your-secret-id
secretKey: your-secret-key
region: ap-shanghai
bucket: your-bucket-name
1.3 引入依赖
XML
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.227</version>
</dependency>
1.4 创建COS客户端配置类
java
/**
* 腾讯云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;
@Bean
public COSClient cosClient() {
// 初始化用户身份信息(secretId, secretKey)
COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
// 设置bucket的区域, COS地域的简称请参照 https://www.qcloud.com/document/product/436/6224
ClientConfig clientConfig = new ClientConfig(new Region(region));
// 生成cos客户端
return new COSClient(cred, clientConfig);
}
}
1.5 创建可复用的CosManager类,专门负责和COS对象存储进行交互,提供文件上传功能。
java
/**
* COS对象存储管理器
*/
@Component
@Slf4j
public class CosManager {
@Resource
private CosClientConfig cosClientConfig;
@Resource
private 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) {
// 构建访问URL
String 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,可能会导致截图错误的页面。
java
// 危险:多线程共享同一个driver
private static final WebDriver webDriver = new ChromeDriver();
// 线程A: driver.get("page1.html") -> 截图
// 线程B: driver.get("page2.html") -> 截图
// 结果:线程 A 可能截到 page2 的内容
优化思路:
ThreadLocal 模式,每个线程使用同一个WebDriver:
java
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写一个定时任务:
java
@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);
}
}
}