Java中用BrowserMob+Selenium捕获请求头却得到空HAR文件
问题背景
目标是读取应用门户的请求头,使用BrowserMob Proxy(BMP)搭配Selenium,但生成的HAR文件中条目始终为空。已尝试移除无头参数、为BMP配置单独SSLProxy,问题仍未解决。环境信息:Java 17、browsermob-core 2.1.5、Selenium 4.7.0。
原代码
package com.example; import io.github.bonigarcia.wdm.WebDriverManager; import net.lightbody.bmp.BrowserMobProxy; import net.lightbody.bmp.BrowserMobProxyServer; import net.lightbody.bmp.client.ClientUtil; import net.lightbody.bmp.core.har.Har; import net.lightbody.bmp.core.har.HarEntry; import net.lightbody.bmp.proxy.CaptureType; import org.openqa.selenium.By; import org.openqa.selenium.Proxy; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.remote.CapabilityType; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; public class webDriver { private static final Logger logger = LoggerFactory.getLogger(webDriver.class); //= LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME); public static void generateUIToken() throws Exception { WebDriver driver = null; BrowserMobProxy proxy = new BrowserMobProxyServer(); proxy.setTrustAllServers(true); proxy.start(0); Har har; Proxy seleniumProxy = ClientUtil.createSeleniumProxy(proxy); String hostIp = "localhost"; seleniumProxy.setHttpProxy(hostIp + ":" + proxy.getPort()); seleniumProxy.setSslProxy(hostIp + ":" + proxy.getPort()); seleniumProxy.setSslProxy("trustAllSSLCertificates"); WebDriverManager.chromedriver().setup(); ChromeOptions options = new ChromeOptions(); options.addArguments("--no-sandbox"); options.addArguments("--headless"); options.addArguments("--ignore-certificate-errors"); options.addArguments("--disable-dev-shm-usage"); DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(CapabilityType.PROXY, seleniumProxy); capabilities.setCapability(CapabilityType.ACCEPT_INSECURE_CERTS,true); capabilities.setCapability(CapabilityType.ACCEPT_SSL_CERTS,true); options.merge(capabilities); try { driver = new ChromeDriver(options); proxy.enableHarCaptureTypes(CaptureType.REQUEST_CONTENT, CaptureType.RESPONSE_CONTENT); proxy.setHarCaptureTypes(CaptureType.REQUEST_HEADERS, CaptureType.RESPONSE_HEADERS); proxy.enableHarCaptureTypes(CaptureType.REQUEST_HEADERS,CaptureType.RESPONSE_HEADERS); proxy.enableHarCaptureTypes(CaptureType.REQUEST_CONTENT, CaptureType.RESPONSE_CONTENT); proxy.newHar("new.example.com"); driver.get("https://new.example.com"); String title = driver.getTitle(); TimeUnit.SECONDS.sleep(10); WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20)); wait.until(ExpectedConditions.elementToBeClickable(By.id("userInput"))); WebElement user_login = driver.findElement(By.id("userInput")); user_login.sendKeys("username"); user_login.submit(); logger.info(user_login + " - " + title); TimeUnit.SECONDS.sleep(10); wait.until(ExpectedConditions.elementToBeClickable(By.id("okta-signin-password"))); WebElement pass_login = driver.findElement(By.id("okta-signin-password")); pass_login.sendKeys("password"); pass_login.submit(); logger.info(pass_login + " - " + title); TimeUnit.SECONDS.sleep(30); logger.info("Current" + driver.getCurrentUrl()); har = proxy.getHar(); List<HarEntry> entries = har.getLog().getEntries(); for (HarEntry entry : entries) { logger.info("Request URL: " + entry.getRequest().getUrl()); logger.info("Entry response status: " + entry.getResponse().getStatus()); logger.info("Entry response text: " + entry.getResponse().getStatusText()); } har.writeTo(new File("test.har")); TimeUnit.SECONDS.sleep(10); driver.quit(); proxy.endHar(); proxy.stop(); } catch (Exception e){ assert driver != null; driver.close(); driver.quit(); } } public static void main(String[] args) throws Exception { generateUIToken(); } }
输出的HAR文件
{"log":{"version":"1.2","creator":{"name":"BrowserMob Proxy","version":"2.1.5","comment":""},"pages":[{"id":"new.example.com","startedDateTime":"2022-12-06T21:46:04.043Z","title":"new.example.com","pageTimings":{"comment":""},"comment":""}],"entries":[],"comment":""}}
问题原因与修复方案
1. SSL代理配置错误
代码中seleniumProxy.setSslProxy("trustAllSSLCertificates");这行完全错误,会覆盖之前正确设置的seleniumProxy.setSslProxy(hostIp + ":" + proxy.getPort());,导致浏览器无法通过BMP代理发送HTTPS请求,自然没有捕获到任何条目。直接删除这行代码即可。
2. HAR初始化时机错误
当前代码是先启动WebDriver,再配置HAR捕获规则并创建newHar,这会导致浏览器启动后到HAR初始化前的所有请求都不会被记录。正确顺序应该是:启动BMP代理 → 配置捕获类型 → 创建newHar → 启动WebDriver。
3. 重复配置捕获类型
代码中多次重复调用enableHarCaptureTypes和setHarCaptureTypes,冗余且可能导致配置冲突,只需要一次调用即可涵盖所有需要捕获的类型。
4. 无头模式优化(可选)
无头模式下可添加--disable-gpu参数,并确保代理配置正确传递,同时BMP已设置proxy.setTrustAllServers(true),配合Chrome的--ignore-certificate-errors可解决证书信任问题。
修复后的代码示例
package com.example; import io.github.bonigarcia.wdm.WebDriverManager; import net.lightbody.bmp.BrowserMobProxy; import net.lightbody.bmp.BrowserMobProxyServer; import net.lightbody.bmp.client.ClientUtil; import net.lightbody.bmp.core.har.Har; import net.lightbody.bmp.core.har.HarEntry; import net.lightbody.bmp.proxy.CaptureType; import org.openqa.selenium.By; import org.openqa.selenium.Proxy; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.remote.CapabilityType; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; public class webDriver { private static final Logger logger = LoggerFactory.getLogger(webDriver.class); public static void generateUIToken() throws Exception { WebDriver driver = null; BrowserMobProxy proxy = new BrowserMobProxyServer(); proxy.setTrustAllServers(true); proxy.start(0); // 1. 先配置HAR捕获类型 proxy.enableHarCaptureTypes( CaptureType.REQUEST_HEADERS, CaptureType.RESPONSE_HEADERS, CaptureType.REQUEST_CONTENT, CaptureType.RESPONSE_CONTENT ); // 2. 创建HAR实例,准备捕获请求 proxy.newHar("new.example.com"); // 配置Selenium代理 Proxy seleniumProxy = ClientUtil.createSeleniumProxy(proxy); String hostIp = "localhost"; seleniumProxy.setHttpProxy(hostIp + ":" + proxy.getPort()); seleniumProxy.setSslProxy(hostIp + ":" + proxy.getPort()); // 移除错误的setSslProxy("trustAllSSLCertificates") WebDriverManager.chromedriver().setup(); ChromeOptions options = new ChromeOptions(); options.addArguments("--no-sandbox"); options.addArguments("--headless"); options.addArguments("--disable-gpu"); // 新增无头模式优化参数 options.addArguments("--ignore-certificate-errors"); options.addArguments("--disable-dev-shm-usage"); DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(CapabilityType.PROXY, seleniumProxy); capabilities.setCapability(CapabilityType.ACCEPT_INSECURE_CERTS, true); capabilities.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true); options.merge(capabilities); try { // 3. 最后启动WebDriver driver = new ChromeDriver(options); driver.get("https://new.example.com"); WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20)); wait.until(ExpectedConditions.elementToBeClickable(By.id("userInput"))); WebElement user_login = driver.findElement(By.id("userInput")); user_login.sendKeys("username"); user_login.submit(); logger.info(user_login + " - " + driver.getTitle()); TimeUnit.SECONDS.sleep(10); wait.until(ExpectedConditions.elementToBeClickable(By.id("okta-signin-password"))); WebElement pass_login = driver.findElement(By.id("okta-signin-password")); pass_login.sendKeys("password"); pass_login.submit(); logger.info(pass_login + " - " + driver.getTitle()); TimeUnit.SECONDS.sleep(30); logger.info("Current URL: " + driver.getCurrentUrl()); Har har = proxy.getHar(); List<HarEntry> entries = har.getLog().getEntries(); for (HarEntry entry : entries) { logger.info("Request URL: " + entry.getRequest().getUrl()); logger.info("Entry response status: " + entry.getResponse().getStatus()); logger.info("Entry response text: " + entry.getResponse().getStatusText()); } har.writeTo(new File("test.har")); driver.quit(); proxy.endHar(); proxy.stop(); } catch (Exception e) { e.printStackTrace(); if (driver != null) { driver.quit(); } proxy.stop(); } } public static void main(String[] args) throws Exception { generateUIToken(); } }
内容的提问来源于stack exchange,提问作者Vishnu Kumar

