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
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/84961.shtml
英文地址,請注明出處:http://en.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—旦需要保存—些重要的…