JAVA网络爬虫(无头浏览器ChromeDriver)

由于工作需要,开始做起了网络爬虫
以爬取小红书为例

创建chromeDriver

	// 创建浏览器
	public void createDriver() {
		// 开启浏览器后需要访问的地址
		String XIAOHOGNSHU_URL = "https://www.xiaohongshu.com/explore";
		
		// 这里我是使用了ip代理模式,因为同一个ip地址频繁请求一个网页会出发反爬虫机制
		HttpProxy httpProxy= new HttpProxy();
		try {
			//获取最优代理ip
			httpProxy=httpProxyService.getBestProxyIp();
			Assert.isNotNull(httpProxy, BaseCodeUtil.OBJECT_NOT_EXIST);
		}catch (Exception e) {
			// 生成5个代理ip
			httpProxyService.createProxy(5);
			httpProxy=httpProxyService.getBestProxyIp();
			Assert.isNotNull(httpProxy, BaseCodeUtil.OBJECT_NOT_EXIST);
		}
		// 过期时间
		String expireTimeStr = httpProxy.getExpire_time();
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		Long expireTime = 0L;
		try {
			// 格式化,计算ip到期时间
			expireTime =simpleDateFormat.parse(expireTimeStr).getTime() - System.currentTimeMillis();
		}catch (Exception e) {
			logger.error(e.getMessage(),e);
		}
		String ip = httpProxy.getIp();
		String ipAndPort = httpProxy.getIp() + ":" + httpProxy.getPort();

		WebDriver driver=null;
		// 获取当前服务器系统
		String osName=System.getProperty("os.name");
		if(osName.contains("Windows")) {
			String webDirverHome = "E:\\code\\chromedriver.exe";
			System.setProperty("webdriver.chrome.driver", webDirverHome);
			ChromeOptions chromeOptions = new ChromeOptions();
			// 启用所有类型的日志并收集所有日志
			LoggingPreferences logPrefs = new LoggingPreferences();
			logPrefs.enable(LogType.PERFORMANCE, Level.ALL);
			logPrefs.enable(LogType.BROWSER, Level.ALL);
			chromeOptions.setCapability(CapabilityType.LOGGING_PREFS, logPrefs);
			// 不加载图片
			chromeOptions.addArguments("blink-settings=imagesEnabled=false");
			// 代理ip
			chromeOptions.addArguments("--proxy-server=http://" + ipAndPort);
			// 生成浏览器
			driver = new ChromeDriver(chromeOptions);
		}else {
			/* linux */
			ChromeOptions options = new ChromeOptions();
			// 禁用沙盒
			options.addArguments("no-sandbox");
			// 禁止加载图片
			options.addArguments("blink-settings=imagesEnabled=false");
			options.addArguments("--whitelisted-ips=\"\"");
			// 代理ip
			options.addArguments("--proxy-server=http://" + ipAndPort);
			// 无界面模式 在Linux中一定是不能唤起浏览器的(很重要)
			options.setHeadless(Boolean.TRUE);
			driver = new ChromeDriver(options);
		}
		// 发起请求
		driver.get(XIAOHOGNSHU_URL);
		// 在页面加载时,寻找 note-wrapper 元素,若在10秒钟未找到,抛出异常
		new WebDriverWait(driver, 10).until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(".note-wrapper")));
//		driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);

		// 获取当前浏览器的信息
		logger.info("Title:" + driver.getTitle());
		logger.info("currentUrl:" + driver.getCurrentUrl());
		
		// 这里我是把生成的浏览器放在一个BlockingQueue中进行管理
		logger.info(">>>>>>>>>>queue:"+queue.size()+">>>>>>>>");
		// 这里是把生成的浏览器放在DelayQueue中进行定时删除ip到期的浏览器(ip到期后不可用,浏览器也会失效)
		logger.info(">>>>>>>>>>delayQueue:"+delayQueue.size()+">>>>>>>>");
		HashMap<String, Object> driverMap = new HashMap<String,Object>();
		String name = UUID.randomUUID().toString();
		driverMap.put("name", name);
		driverMap.put("driver", driver);
		driverMap.put("ip", ip);
		driverMap.put("failNum", 0);
		logger.info("################浏览器信息#####################");
		logger.info("name:" + name + ";ip:" + ip + ";failNum:" + 0);
		queue.offer(driverMap);
		WebDriverTask webDriverTask = new WebDriverTask(name,expireTime);
		delayQueue.offer(webDriverTask);
		logger.info("<<<<<<<<<<queue:"+queue.size()+"<<<<<<<<");
		logger.info("<<<<<<<<<<delayQueue:"+delayQueue.size()+"<<<<<<<<");
	}

创建浏览器是在项目初始化的时候进行的,我这里是直接创建了5个浏览器放在容器中,并定时的去更新浏览器

	private int webDriverNum = 5;

	public void initDriver() {
		
		// 循环创建浏览器
		for(int i=0;i<webDriverNum;i++) {
			System.out.println("========initDriver 第" + (i + 1) + "个===================");
			try {
				createDriver();
			}catch (Exception e) {
				logger.error(e.getMessage(),e);
				logger.error("第" + (i + 1) + "个浏览器启动失败");
			}
		}
		
		// 再启动一个线程进行轮询工作
		new Thread(new Runnable() {

			@Override
			public void run() {

				while(delayQueue.size() > 0) {
					try {
						// 当delayQueue中的浏览器时间到期后会取出来,否则将阻塞在这里
						WebDriverTask webDriverTask = delayQueue.take();
						String name = webDriverTask.getName();
						// 然后用轮询的方式一个一个进行名字比较,true 则销毁并创建新的浏览器,false 则重新塞回队列中
						for(int i = 0; i < queue.size(); i++) {
							Map<String, Object> driverMap = queue.take();
							String driverName = (String) driverMap.get("name");
							if(StringUtils.equals(driverName, name)) {
								//销毁
								WebDriver driver = (WebDriver)driverMap.get("driver");
								driver.quit();
								//新建浏览器并塞入队列
								createDriver();
							}else {
								//塞回队列
								queue.put(driverMap);
							}
						}
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}).start();
	}

我之前用的是 @PostConstruct 进行初始化的,后来可能是生命周期初始化顺序有点问题,后来改成在 Listerner中使用 SpringContextHolder.getBean(ChromeDriverService.class).initDriver(); 的方法在程序启动的时候开启5个浏览器

	// 项目关闭的时候,记得结束掉开启的浏览器,释放资源
	@PreDestroy
	public void destoryDriver() {
		logger.info("=======销毁前,关闭webDriver======");
		while(queue.size()>0) {
			logger.info(">>>>>>>>>>销毁前,webDriver_count:"+queue.size()+">>>>>>>>");
			Map<String, Object> driverMap = queue.poll();
			WebDriver driver = (WebDriver)driverMap.get("driver");
			driver.quit();
			delayQueue.clear();
			logger.info("<<<<<<<<<<销毁后,webDriver_count:"+queue.size()+"<<<<<<<<");
		}
	}

请求小红书

	public UserKocAuth xiaohongshuAuthentication(String homePageUrl) {
		String chromeDriver =null;
		Map<String, Object> driverMap = null;
		UserKocAuth userKocAuth = new UserKocAuth();
		try {
			long before = System.currentTimeMillis();
			System.out.println("无头浏览器开始,before:" + before);
			// 获取浏览器
			driverMap = chromeDriverService.getDriver();
			// 发起请求
			chromeDriver = doChromeDriver(homePageUrl,driverMap);

			long after = System.currentTimeMillis();
			System.out.println("无头浏览器结束,after:" + after);
			System.out.println("无头浏览器花费时间,all:" + (after - before));
			
			// 用来解析浏览器返回的HTML页面(放在之后一篇再讲Jsoup)
			Document doc = Jsoup.parse(chromeDriver);
			logger.info(doc.text());

			Elements cardElement=doc.getElementsByClass("card-info").get(0).getElementsByClass("info-number");
			logger.info("关注:"+cardElement.get(0).text());
			logger.info("粉丝:"+cardElement.get(1).text());
			logger.info("获赞与收藏:"+cardElement.get(2).text());

			String attentionStr = cardElement.get(0).text();
			String fansStr = cardElement.get(1).text();
			String likeNumStr = cardElement.get(2).text();

			Integer attention = numberEndWithCN(attentionStr);
			Integer fansNum = numberEndWithCN(fansStr);
			Integer likeNum = numberEndWithCN(likeNumStr);

			Element element = doc.getElementsByClass("author-container").first().getElementsByClass("left").first();
			String face = element.select("img").first().attr("src");
			logger.info("头像:" + face);
			
			String name = doc.getElementsByClass("user-name").get(0).getElementsByClass("name-detail").text();
			logger.info("名字:" + name);
			
			// 这里我是写了一个方法,切割url链接,获取url中的id
			Map<String, String> paramsMap = GetParamsMapFromUrlUtil.urlSplit(homePageUrl);
			String platformId = paramsMap.get("appuid");

			userKocAuth.setPlatformId(platformId);
			userKocAuth.setFansNum(fansNum);
			userKocAuth.setAttention(attention);
			userKocAuth.setLikeNum(likeNum);
			userKocAuth.setPlatformFace(face);
			userKocAuth.setPlatformName(name);
		}catch (Exception e) {
			logger.error(e.getMessage(),e);
			int failNum = (Integer)driverMap.get("failNum");
			driverMap.put("failNum", ++failNum);
			throw new IllegalParameterException(ErrorCodeUtil.AUTHENTICATION_EXCEPTION);
		}finally {
			// 归还浏览器
			chromeDriverService.returnDriver(driverMap);
		}
		return userKocAuth;
	}

具体的chromeDriver请求方法

	public String doChromeDriver(String url, Map<String,Object> driverMap) {
		String pageSource = null;
		Long chromeDriver = System.currentTimeMillis();

		WebDriver driver = (WebDriver)driverMap.get("driver");

		Long chromeDriverOver = System.currentTimeMillis();
		logger.info("结束唤起浏览器,开始渲染页面,chromeDriverOver:" + chromeDriverOver);
		logger.info("唤起页面一共花费,all:" + (chromeDriverOver - chromeDriver));
		// 发起请求
		driver.get(url);
		long urlOver = System.currentTimeMillis();
		logger.info("获取url,urlOver:" + urlOver);
		logger.info("获取url共花费,all:" + (urlOver - chromeDriverOver));

		// 页面加载直到页面获取到 author-container 元素,10秒后若没找到该元素,则抛出异常
		new WebDriverWait(driver, 10).until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(".author-container")));

		pageSource = driver.getPageSource();
		logger.info("获取结果pageSource:" + pageSource);
		return pageSource;
	}

小结

1、每次创建浏览器的时候我都会去Redis中取一个ip,如果没有ip,我会创建5个ip放在Redis中,问题就在于:如果有一个ip过期了,程序中就会从剩下的四个ip中去取一个,这样一来,创建的5个浏览器中一定会有两个浏览器共用同一个ip,循序下去5个浏览器就会使用容一个ip,这样一来就违背了我们的初衷,并且当这个ip过期之后,5个浏览器就同时崩溃,且再次创建浏览器的时候,只会创建一个浏览器。所以需要定时去刷新ip的数量和浏览器的数量
2、在项目启动时我是另外开了一个线程去做一个死循环的轮询,定时查看过期ip,当有ip过期,则关闭该浏览器并重新启动一个新ip的浏览器
3、结束项目的时候一定要关闭浏览器释放资源,不然会循序下去会占用CPU大量的资源

无头浏览器返回的HTML页面,可以用Jsoup进行解析,详情可以看Jsoup用法

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值