我的问题
我是在java中使用selenium+ChromeDriver采集数据时,需要扫码授权为了避免频繁扫码,这样就要保持浏览器一直开着,不能每次采集完之后不能使用driver.quit()退出方法。ChromeDriver长时间运行采集内存不断增加,慢慢导致程序崩溃。在网上搜了好多解决ChromeDriver内存溢出的方法都无效。被逼无奈转换思路。
我的思路
既然没办法解决ChromeDriver内存溢出,那能不能通过端口来监听ChromeDriver进程内存,如果内存达到阈值,那就直接关闭这个ChromeDriver进程,在创建一个ChromeDriver控制原来的浏览器?实现继续采集,通过实践是可以的。
解决方式
通过ChromeDriver端口获取使用的内存,如果达到阈值则进行关闭原来的ChromeDriver并用原端口创建一个新的ChromeDriver指定控制的浏览器端口。
下面直接给出代码自己参考修改,里面通过端口监听内存的方法可以看我另外一篇文章
链接: JAVA在windows和linux系统通过端口获取进程、内存、关闭进程方法
/**
* 检查driver内存使用情况,超出阈值则替换
*/
public static boolean checkDriverUsage(ChromeDriverContainer driverContainer) {
// 获取driver使用端口
log.info("检查driver内存使用情况开始...");
Integer port = driverContainer.getManageParam().getPort();
Integer browserPort = driverContainer.getManageParam().getBrowserPort();
if (port == null || browserPort == null) {
log.info("检查driver异常---->driverContainer端口或浏览器端口为空:{},{}", driverContainer, browserPort);
return false;
}
// 根据端口获取内存使用情况
String osName = System.getProperty("os.name");
log.info("操作系统名称:{}", osName);
String pid = null;
long memoryUsageByPid = 0;
try {
if (osName.toLowerCase().contains("windows")) {
pid = WindowsProcessMemoryUtil.findProcessIdUsingPort(port);
if (StringUtils.isEmpty(pid)) {
log.info("检查driver异常---->通过端口获取进程PID为空:{}", port);
return false;
}
// 获取内存
memoryUsageByPid = WindowsProcessMemoryUtil.getMemoryUsageByPid(pid);
} else if (osName.toLowerCase().contains("linux")) {
pid = LinuxProcessMemoryUtil.findProcessIdUsingPort(port);
if (StringUtils.isEmpty(pid)) {
log.info("检查driver异常---->通过端口获取进程PID为空:{}", port);
return false;
}
// 获取内存
memoryUsageByPid = LinuxProcessMemoryUtil.getMemoryUsageByPid(pid);
}
log.info("获取端口port:{}, 进程PID:{},已使用内存KB:{}", port, pid, memoryUsageByPid);
if (memoryUsageByPid > 300 * 1024) { // 300mb
// if (true) {
// 超出监控重新创建
log.info("超出阈值进行替换...");
// 关闭进程
if (osName.toLowerCase().contains("windows")) {
WindowsPortKillerUtil.killProcess(pid);
} else if (osName.toLowerCase().contains("linux")) {
LinuxPortKillerUtil.killProcess(pid);
}
//增加延迟防止关闭进程慢导致创建driver失败
ThreadUtil.sleep(3000);
// 重新创建chromeDriver
ChromeDriver driver = ChromeDriverUtils.getChromeDriverContainer(ChromeDriverParam.builder()
.headless(false)
.port(driverContainer.getManageParam().getPort())// 指定原端口
.functionOptions(options -> {
options.setExperimentalOption("debuggerAddress", "localhost:" + browserPort);// 指定原浏览器端口
BrowserMobProxyServer proxy = driverContainer.getProxy();
if (proxy != null) {
Integer proxyPort = proxy.getPort();
proxy.stop();
BrowserMobProxyServer browserMobProxy = new BrowserMobProxyServer();
browserMobProxy.start(proxyPort);
browserMobProxy.enableHarCaptureTypes(CaptureType.REQUEST_CONTENT, CaptureType.RESPONSE_CONTENT);
browserMobProxy.setHarCaptureTypes(CaptureType.RESPONSE_CONTENT);
browserMobProxy.newHar("kk:" + proxyPort);
Proxy seleniumProxy = ClientUtil.createSeleniumProxy(browserMobProxy);
// 实例化Selenium
options.setProxy(seleniumProxy);
options.setAcceptInsecureCerts(true);
driverContainer.setProxy(browserMobProxy);
}
return options;
})
.build()).getDriver();
driverContainer.setDriver(driver);
}
} catch (Exception e) {
log.error("检查driver异常---->", e);
return false;
}
log.info("检查driver内存使用情况结束...");
return true;
}
我上面创建driver的时候使用了代理,所以也要停掉之前的代理用原来的代理端口创建一个新的代理。在创建新driver控制原来浏览器的时候我这遇到了一个问题:通过addArguments(“debuggerAddress”, “127.0.0.1:54223”)这种方式是可以控制原浏览器的,但是每次创建的时候又新打开了一个页面,如果再用这种方式创建driver控制浏览器就控制不了,在网上找到这个setExperimentalOption这个方法控制原浏览器就解决了。这我没找到问题原因有知道的大佬希望可以告知下。谢谢
我获取原driver控制浏览器端口是这么获取的一并放出来自己参考完善。
ChromeDriver driver = getChromeDriver(chromeDriverService, options);
Object o = driver.getCapabilities().asMap().get("goog:chromeOptions");
//转成map
Map<String, Object> map = (Map<String, Object>) o;
if (map != null) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getKey().equals("debuggerAddress")) {
String value = entry.getValue().toString();
String[] split = value.split(":");
if (split.length == 2) {
String ip = split[0];
String browserPort = split[1];
manageParam.setBrowserPort(Integer.parseInt(browserPort));
}
}
}
}
我代码里面的创建driver方法都是包装了的,文章代码里面获取创建driver方式改成你们自己的即可。如果觉得有用麻烦点点赞,谢谢!