无需配置代理,如何用Java+Selenium生成HAR文件提取网络资源?
嘿,我完全懂你不想折腾代理又要抓取Selenium多页面网络流量的需求!不用愁,咱们直接用Chrome DevTools Protocol(CDP)和Selenium结合就能搞定,全程不需要任何代理配置,纯Java实现就好。
核心思路
Selenium从3.x版本开始就支持和Chrome DevTools Protocol交互了,我们可以直接通过CDP启用浏览器的网络监听,捕获每个页面加载过程中的所有请求和响应数据,之后把这些数据转成JSON或者标准的HAR格式保存,完美避开代理配置的麻烦。
依赖准备
首先确保你的项目里引入了Selenium的Java依赖,还有Gson用来处理JSON序列化(如果要生成HAR也需要它)。如果用Maven的话,把下面的依赖加到pom.xml里:
<dependencies> <!-- Selenium Java --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>4.15.0</version> </dependency> <!-- Gson for JSON processing --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.10.1</version> </dependency> <!-- 可选:用WebDriverManager自动管理ChromeDriver版本,不用手动下载驱动 --> <dependency> <groupId>io.github.bonigarcia</groupId> <artifactId>webdrivermanager</artifactId> <version>5.6.0</version> </dependency> </dependencies>
完整代码实现
下面是一个可以直接运行的示例,实现多页面导航并捕获每个页面的网络流量,保存为JSON文件:
import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.devtools.DevTools; import org.openqa.selenium.devtools.v118.network.Network; import org.openqa.selenium.devtools.v118.network.model.Request; import org.openqa.selenium.devtools.v118.network.model.Response; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; public class SeleniumNetworkCapture { public static void main(String[] args) { // 可选:用WebDriverManager自动下载并配置ChromeDriver // WebDriverManager.chromedriver().setup(); // 配置Chrome浏览器选项,比如开启最大化窗口,按需添加无头模式等 ChromeOptions options = new ChromeOptions(); options.addArguments("--start-maximized"); // 初始化ChromeDriver ChromeDriver driver = new ChromeDriver(options); driver.manage().timeouts().pageLoadTimeout(30, TimeUnit.SECONDS); // 获取DevTools会话,用来和CDP交互 DevTools devTools = driver.getDevTools(); devTools.createSession(); // 启用Network域,开始监听网络请求 devTools.send(Network.enable(Optional.empty(), Optional.empty(), Optional.empty())); // 存储每个页面的网络流量条目 List<NetworkTrafficEntry> trafficEntries = new ArrayList<>(); // 监听请求发送事件:捕获每个请求的详细信息 devTools.addListener(Network.requestWillBeSent(), requestEvent -> { Request request = requestEvent.getRequest(); trafficEntries.add(new NetworkTrafficEntry( requestEvent.getRequestId(), request.getUrl(), "REQUEST", request )); }); // 监听响应接收事件:捕获每个响应的详细信息 devTools.addListener(Network.responseReceived(), responseEvent -> { Response response = responseEvent.getResponse(); trafficEntries.add(new NetworkTrafficEntry( responseEvent.getRequestId(), response.getUrl(), "RESPONSE", response )); }); // ====== 第一个页面导航 ====== driver.get("https://example.com"); // 这里可以加显式等待,确保页面完全加载(比如等待某个核心元素出现) // new WebDriverWait(driver, 10).until(ExpectedConditions.visibilityOfElementLocated(By.tagName("h1"))); // 保存第一个页面的流量到JSON文件 saveTrafficToJson(trafficEntries, "page1_network_traffic.json"); trafficEntries.clear(); // 清空列表,准备捕获下一个页面的流量 // ====== 第二个页面导航 ====== driver.get("https://example.org"); // 同样添加显式等待 saveTrafficToJson(trafficEntries, "page2_network_traffic.json"); // 清理资源 devTools.send(Network.disable()); driver.quit(); } // 自定义类,用来封装单个网络请求/响应的信息 static class NetworkTrafficEntry { private String requestId; private String url; private String type; // REQUEST 或 RESPONSE private Object data; // 存储Request或Response对象 public NetworkTrafficEntry(String requestId, String url, String type, Object data) { this.requestId = requestId; this.url = url; this.type = type; this.data = data; } // Getter和Setter方法,Gson需要这些来序列化 public String getRequestId() { return requestId; } public void setRequestId(String requestId) { this.requestId = requestId; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getType() { return type; } public void setType(String type) { this.type = type; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } } // 将网络流量列表保存为格式化的JSON文件 private static void saveTrafficToJson(List<NetworkTrafficEntry> entries, String filePath) { Gson gson = new GsonBuilder().setPrettyPrinting().create(); String jsonContent = gson.toJson(entries); try (FileWriter writer = new FileWriter(filePath)) { writer.write(jsonContent); System.out.println("网络流量已成功保存到:" + filePath); } catch (IOException e) { System.err.println("保存JSON文件时出错:" + e.getMessage()); e.printStackTrace(); } } }
生成标准HAR文件(可选)
如果需要生成符合标准的HAR文件(可以直接导入Chrome开发者工具查看),我们可以基于捕获到的请求和响应数据构造HAR格式的结构。下面是补充的代码片段:
// 新增HAR相关的实体类,对应HAR标准结构 static class HARLog { private String version = "1.2"; private HARCreator creator = new HARCreator("Selenium CDP Capture", "1.0"); private List<HAREntry> entries = new ArrayList<>(); // Getter和Setter public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public HARCreator getCreator() { return creator; } public void setCreator(HARCreator creator) { this.creator = creator; } public List<HAREntry> getEntries() { return entries; } public void setEntries(List<HAREntry> entries) { this.entries = entries; } } static class HARCreator { private String name; private String version; public HARCreator(String name, String version) { this.name = name; this.version = version; } // Getter和Setter public String getName() { return name; } public void setName(String name) { this.name = name; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } } static class HAREntry { private HARRequest request; private HARResponse response; // Getter和Setter public HARRequest getRequest() { return request; } public void setRequest(HARRequest request) { this.request = request; } public HARResponse getResponse() { return response; } public void setResponse(HARResponse response) { this.response = response; } } static class HARRequest { private String url; private String method; private List<HARHeader> headers = new ArrayList<>(); // Getter和Setter public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public List<HARHeader> getHeaders() { return headers; } public void setHeaders(List<HARHeader> headers) { this.headers = headers; } } static class HARResponse { private int status; private String statusText; private List<HARHeader> headers = new ArrayList<>(); // Getter和Setter public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public String getStatusText() { return statusText; } public void setStatusText(String statusText) { this.statusText = statusText; } public List<HARHeader> getHeaders() { return headers; } public void setHeaders(List<HARHeader> headers) { this.headers = headers; } } static class HARHeader { private String name; private String value; public HARHeader(String name, String value) { this.name = name; this.value = value; } // Getter和Setter public String getName() { return name; } public void setName(String name) { this.name = name; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } // 将捕获的流量转换为标准HAR格式并保存 private static void saveTrafficToHAR(List<NetworkTrafficEntry> entries, String filePath) { HARLog harLog = new HARLog(); // 用Map来临时存储请求,方便和响应配对 java.util.Map<String, HAREntry> requestMap = new java.util.HashMap<>(); for (NetworkTrafficEntry entry : entries) { if ("REQUEST".equals(entry.getType())) { Request req = (Request) entry.getData(); HAREntry harEntry = new HAREntry(); HARRequest harReq = new HARRequest(); harReq.setUrl(req.getUrl()); harReq.setMethod(req.getMethod()); // 转换请求头 req.getHeaders().forEach((k, v) -> harReq.getHeaders().add(new HARHeader(k, v))); harEntry.setRequest(harReq); requestMap.put(entry.getRequestId(), harEntry); } else if ("RESPONSE".equals(entry.getType())) { Response res = (Response) entry.getData(); HAREntry harEntry = requestMap.get(entry.getRequestId()); if (harEntry != null) { HARResponse harRes = new HARResponse(); harRes.setStatus(res.getStatus()); harRes.setStatusText(res.getStatusText()); // 转换响应头 res.getHeaders().forEach((k, v) -> harRes.getHeaders().add(new HARHeader(k, v))); harEntry.setResponse(harRes); } } } // 将配对好的条目加入HAR日志 harLog.getEntries().addAll(requestMap.values()); // 保存为HAR文件 Gson gson = new GsonBuilder().setPrettyPrinting().create(); String harContent = gson.toJson(harLog); try (FileWriter writer = new FileWriter(filePath)) { writer.write(harContent); System.out.println("HAR文件已成功保存到:" + filePath); } catch (IOException e) { System.err.println("保存HAR文件时出错:" + e.getMessage()); e.printStackTrace(); } }
然后在主方法里,把saveTrafficToJson换成saveTrafficToHAR就能生成标准HAR文件了。
注意事项
- CDP版本匹配:代码里用的是
v118的Network API,要确保你的Chrome浏览器和ChromeDriver版本和这个API版本对应,或者用WebDriverManager自动管理版本,避免版本不兼容的问题。 - 页面等待策略:不要只依赖
pageLoadTimeout,最好用显式等待(比如等待页面核心元素加载完成),确保所有网络请求都已经完成再捕获流量。 - 多页面清理:每个页面导航前一定要清空流量列表,避免不同页面的流量混在一起。
- Firefox支持:如果用Firefox,同样可以通过CDP实现,不过API细节会略有不同,Chrome的CDP支持是最成熟的。
内容的提问来源于stack exchange,提问作者Bala
相关产品推荐
相关产品推荐

