> 技术文档 > htmlUnit和Selenium的区别以及使用BrowserMobProxy捕获网络请求_browsermob-proxy

htmlUnit和Selenium的区别以及使用BrowserMobProxy捕获网络请求_browsermob-proxy


1. Selenium:浏览器自动化之王

核心定位
        跨平台、跨语言的浏览器操控框架,通过驱动真实浏览器实现像素级用户行为模拟

技术架构

核心特性

  • 支持所有主流浏览器(含移动端模拟)

  • 精确的DOM元素定位(XPath/CSS/ID)

  • 屏幕截图与视频录制功能

  • 分布式测试能力(Selenium Grid)

2. HtmlUnit:无头浏览器轻骑兵

核心定位
        纯Java实现的无界面浏览器引擎,专为服务端自动化场景优化。

技术架构

核心特性

  • 毫秒级页面加载速度

  • 线程安全设计

  • 内置基础AJAX支持

  • Cookie自动管理

3. BrowserMobProxy:网络流量手术刀

核心定位
基于Netty开发的HTTP代理服务器,专为Web流量监控与操控设计。

技术架构

核心特性

  • 实时流量镜像

  • 请求/响应内容篡改

  • 性能指标采集(TTFB等)

  • 支持HTTPS中间人攻击

能力对比矩阵

维度 Selenium HtmlUnit BrowserMobProxy 执行环境 真实浏览器进程 纯JVM环境 独立代理服务 JS支持 完整ES6+ ES5(Rhino引擎) 不涉及 网络延迟模拟 需扩展 原生支持 精确到毫秒级控制 跨域请求处理 受同源策略限制 自动绕过 全流量穿透 移动端调试 完整设备模拟 仅UA伪装 流量分析 典型应用场景 自动化测试 服务端爬虫 接口监控

安装浏览器:Google Chrome谷歌为例

4、selenium和BrowserMobProxy捕获网络请求实例

驱动下载:Chrome for Testing 的可用性(135后版本)chromedriver.storage.googleapis.com/index.html(旧版本驱动)
安装需记住安装位置,启动时需要设置驱动路径

代码实现

getDynamicCrawlersDocument方法为htmlunit的请求监控使用
getParamsByNodeUrl方法为selenium的请求实现,selenium需要驱动支持,可以获取到复杂的请求接口

依赖:

 org.seleniumhq.selenium selenium-java 4.10.0 net.lightbody.bmp browsermob-core 2.1.5 org.seleniumhq.selenium selenium-chrome-driver 4.1.0

代码工具类:

package com.zzkj.zei.utils;import com.zzkj.zei.component.ServerConfig;import lombok.extern.slf4j.Slf4j;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.core.har.HarRequest;import net.lightbody.bmp.mitm.manager.ImpersonatingMitmManager;import net.lightbody.bmp.proxy.CaptureType;import org.apache.commons.lang3.ObjectUtils;import org.htmlunit.BrowserVersion;import org.htmlunit.FailingHttpStatusCodeException;import org.htmlunit.ScriptException;import org.htmlunit.WebClient;import org.htmlunit.html.HtmlPage;import org.jsoup.Jsoup;import org.jsoup.nodes.Document;import org.openqa.selenium.JavascriptExecutor;import org.openqa.selenium.Proxy;import org.openqa.selenium.WebDriver;import org.openqa.selenium.chrome.ChromeDriver;import org.openqa.selenium.chrome.ChromeOptions;import org.openqa.selenium.remote.CapabilityType;import org.openqa.selenium.support.ui.WebDriverWait;import org.htmlunit.ProxyConfig;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;import java.net.InetSocketAddress;import java.net.UnknownHostException;import java.util.concurrent.TimeUnit;import java.time.Duration;import java.util.*;/** * FileName: SeleniumUtils * Author: wzk * Date:2025/4/29 11:31 */@Component@Slf4jpublic class SeleniumUtils { private static String SELENIUM_PATH; @Value(\"${selenium.chromedriver_path}\") private String seleniumPath; // 非静态变量接收注入 @PostConstruct public void init() { SELENIUM_PATH = this.seleniumPath; } private final static List UA_LIST = Arrays.asList( \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.0.0 Safari/537.36\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.0.0 Safari/537.36\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.0.0 Safari/537.36\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.0.0 Safari/537.36\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.0.0 Safari/537.36\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.0.0 Safari/537.36\" , \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Safari/537.36\"); public static void main(String[] args) {// List interfaceNodeDataList = getParamsByNodeUrl(ce,true); List interfaceNodeDataList = getDynamicCrawlersDocument(gfwj1, 1000, true); for (interfaceNodeData interfaceNodeData : interfaceNodeDataList) { log.info(\"方法:{} 链接:{} 请求参数:{}\" ,interfaceNodeData.getMethod(),interfaceNodeData.getUrl(), interfaceNodeData.getParams()); } } public static List getDynamicCrawlersDocument(String url, Integer waitTime, boolean javaScriptEnabled) { List interfaceNodeDatas = new ArrayList(); // 1. 启动BrowserMob代理 BrowserMobProxy proxy = new BrowserMobProxyServer(); proxy.start(0); // 自动分配端口 int proxyPort = proxy.getPort(); try { // 2. 配置HtmlUnit使用代理 WebClient browser = new WebClient(BrowserVersion.CHROME); browser.getOptions().setProxyConfig(new ProxyConfig(\"localhost\",proxyPort,\"http\")); // 启用HTTPS支持(忽略证书验证) browser.getOptions().setSSLInsecureProtocol(\"ssl\"); //解决动态页面抓取不到信息问题 browser.getOptions().setCssEnabled(false); browser.getOptions().setJavaScriptEnabled(javaScriptEnabled); browser.getOptions().setThrowExceptionOnScriptError(false); browser.getOptions().setUseInsecureSSL(true); // 设置自定义的错误处理类 browser.setJavaScriptErrorListener(new JsoupHtmlUintUtils.MyJSErrorListener()); // 开始捕获请求 proxy.newHar(\"zzjk\"); HtmlPage page = null; page = browser.getPage(url); // 等待后台脚本执行时间 browser.waitForBackgroundJavaScript(waitTime);// String pageAsXml = page.asXml();// document = Jsoup.parse(pageAsXml.replaceAll(\"\\\\\", \"\"));// document.setBaseUri(url); // 5. 获取并分析HAR数据 Har har = proxy.getHar(); processHarEntries(har, url, interfaceNodeDatas); } catch (ScriptException e) { log.error(\"getDynamicCrawlersDocument页面:{} JavaScript 异常:{}\", url, e.getMessage()); } catch (UnknownHostException e) { log.error(\"getDynamicCrawlersDocument页面:{} 无法解析或找到指定的主机名:{}\", url, e.getMessage()); } catch (FailingHttpStatusCodeException e) { log.error(\"getDynamicCrawlersDocument页面:{} HTTP 状态异常:{}\", url, e.getStatusCode()); } catch (Exception e) { log.error(\"getDynamicCrawlersDocument页面:{} 获取页面异常:{}\", url, e.getMessage()); } finally { // 6. 清理资源 proxy.stop(); } return interfaceNodeDatas; } public static List getParamsByNodeUrl(String url,Boolean isTime){ long stat = new Date().getTime(); System.setProperty(\"webdriver.chrome.driver\", SELENIUM_PATH); //设置chrome驱动程序的路径 BrowserMobProxy proxy = new BrowserMobProxyServer(); proxy.start(0); // 自动选择端口 // 获取Selenium的Proxy对象 Proxy seleniumProxy = ClientUtil.createSeleniumProxy(proxy); ChromeOptions opt = new ChromeOptions(); opt.addArguments(); opt.addArguments( \"--headless\",  // 开启无界面模式 \"--disable-gpu\", // 禁用gpu \"--remote-allow-origins=*\", // 允许所有源访问 \"--ignore-certificate-errors\", // 忽略证书错误 \"--user-agent=\" + UA_LIST.get(0), // 设置请求头 \"--no-sandbox\", // 禁用沙盒,减少权限检查 \"--disable-dev-shm-usage\", // 避免共享内存问题 \"--log-level=3\", // 禁用 Chrome 日志 \"--blink-settings=imagesEnabled=false\", // 禁止图片加载 \"--disable-extensions\", // 禁用扩展 \"--disable-javascript\", // 禁用 JavaScript(如果目标页面不需要 JS) \"--disable-css\", // 禁用 CSS 渲染(按需) \"--disable-fonts\", // 禁用字体加载 \"--dns-prefetch-disable\", // 禁用 DNS 预解析 \"--disk-cache-size=0\", // 禁用 缓存 \"--disable-cache\" // 禁用 缓存 ); opt.setCapability(CapabilityType.PROXY, seleniumProxy); WebDriver driver = new ChromeDriver(opt); //初始化一个chrome驱动实例,保存到driver中 try { driver.manage().window().maximize(); driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS); // 启用隐式等待 return seleniumGetDocument(driver,proxy,url,isTime); } catch (Exception e) { log.info(\"错误URL: \" + driver.getCurrentUrl()); e.printStackTrace(); } finally { driver.quit(); // 自动清理Cookies和会话 proxy.stop(); long end = new Date().getTime(); log.info(\"selenium参数获取时间\" + (end - stat)); } return null; } public static List seleniumGetDocument(WebDriver driver, BrowserMobProxy proxy, String url,Boolean isTime) { List dataList = new ArrayList(); try { // 启用MITM抓取HTTPS proxy.setMitmManager(new ImpersonatingMitmManager.Builder()  .trustAllServers(true)  .build()); proxy.setHarCaptureTypes(CaptureType.REQUEST_CONTENT, CaptureType.RESPONSE_CONTENT); proxy.newHar(\"zzkj\"); // 访问页面并等待 driver.get(url); // 等待页面加载完成 new WebDriverWait(driver, Duration.ofSeconds(30)).until(  webDriver -> ((JavascriptExecutor) webDriver).executeScript(\"return document.readyState\").equals(\"complete\") ); // 验证请求是否稳定 validationAll(proxy); if (isTime){ Thread.sleep(10000); // 休眠10秒 } // 处理HAR数据 Har har = proxy.getHar(); processHarEntries(har, url, dataList); } catch (Exception e) { e.printStackTrace(); } return dataList; } private static void validationAll(BrowserMobProxy proxy) throws InterruptedException { int retries = 0; int stableCount = 0; int lastEntrySize = 0; while (retries < 30 && stableCount = 30) { log.info(\"----------------------- 请求验证稳定超时 -----------------------\"); } } // 处理 HAR 条目并过滤 private static void processHarEntries(Har har, String baseUrl, List interfaceNodeDatas) { har.getLog().getEntries().forEach(entry -> { HarRequest request = entry.getRequest(); String method = request.getMethod(); String toUrl = request.getUrl(); log.info(\"检测链接:{}\",toUrl); // 过滤 boolean isStaticResource = toUrl.matches(\".*\\\\.(css|js|png|jpg|jpeg|gif|ico|woff|woff2|svg|mp4|mp3)(\\\\?.*)?$\"); boolean isStaticPath = toUrl.contains(\"/material/\") ||  toUrl.contains(\"/fonts/\") ||  toUrl.contains(\"/script/\") ||  toUrl.contains(\"/login/\") ||  toUrl.contains(\"/images/\"); if ((\"POST\".equalsIgnoreCase(method) || \"GET\".equalsIgnoreCase(method)) &&//  !filterOutsideUrl(baseUrl, toUrl) &&  isCurrentNodeUrl(baseUrl,toUrl) &&  !isStaticResource &&  !isStaticPath) { interfaceNodeData interfaceNodeData = new interfaceNodeData(); interfaceNodeData.setUrl(toUrl); interfaceNodeData.setMethod(method); interfaceNodeData.setData(entry.getResponse().getContent().getText()); interfaceNodeData.setParams(ObjectUtils.isEmpty(request.getPostData()) ? \"\" : request.getPostData().getText()); interfaceNodeDatas.add(interfaceNodeData); } }); } private static boolean isCurrentNodeUrl(String sourceUrl, String targetUrl) { // 移除协议、转为小写、处理末尾斜杠和index.html String normalizedSource = normalizeUrl(sourceUrl); String normalizedTarget = normalizeUrl(targetUrl); // 判断目标URL是否以当前节点URL开头 return !normalizedSource.contains(normalizedTarget); } /** * 标准化URL处理 */ private static String normalizeUrl(String url) { // 移除协议头并转为小写 String normalized = url.replaceAll(\"^(http://|https://)\", \"\").toLowerCase(); // 移除末尾的 \"/\" 和 \"index.html\" normalized = normalized.replaceAll(\"/+$\", \"\") .replaceAll(\"/index\\\\.html$\", \"\"); return normalized; }}
package com.zzkj.zei.utils;import lombok.Data;/** * FileName: interfaceNodeData * Author: wzk * Date:2025/4/30 17:00 */@Datapublic class interfaceNodeData { String url; String data; String method; String params; interfaceNodeData(){ } interfaceNodeData(String url,String method,String data,String params){ this.url = url; this.method = method; this.data = data; this.params = params; }}

 

济南婚介机构