Java基于Selenium动态抓取页面

前情介绍

前段时间开发了一个功能,通过HttpClient访问某个页面,获取页面的全部html内容;之后通过抓取过来的整个页面展示在自己的网页中;但是过了一段时间之后,网页升级了,网页中的图片都变成了动态加载,直接通过HttpClient无法获取完整的页面内容,图片都是懒加载状态无法展示正确内容。

解决

思路

既然没办法直接抓取静态页面,那么可以尝试动态抓取页面;如何抓取动态页面呢,本文里采用了Selenium进行动态抓取页面。通过Selenium模拟正常打开一个浏览器,浏览网页的过程,当浏览到页面的最下方整个页面加载完毕,此时再抓取整个网页的内容就可以完整的获取页面内容。
Selenium的WebDriver可以模拟不同浏览器,本文采用的是Chrome浏览器,其他支持的浏览器大家可以通过查看WebDriver的实现类来获得答案。

编码

引用Selenium库的jar包

	<dependency>
		<groupId>org.seleniumhq.selenium</groupId>
		<artifactId>selenium-java</artifactId>
		<version>3.141.59</version>
	</dependency>

因为要模拟Chrome浏览器,所以要下载chromedriver,到这基础的工作就做完了,接下来是开发代码。废话不多说,直接上代码。

public String selenium(String url) throws InterruptedException {
        // 设置 chromedirver 的存放位置
        System.getProperties().setProperty("webdriver.chrome.driver", "D:/lingy/chromedriver_win32/chromedriver.exe");
        // 设置无头浏览器,这样就不会弹出浏览器窗口
        ChromeOptions chromeOptions = new ChromeOptions();
        chromeOptions.addArguments("--headless");
        Long scrollSize = 1000L;
        WebDriver webDriver = new ChromeDriver(chromeOptions);
        webDriver.get(url);
        //设置浏览器的宽高
        Dimension dimension = new Dimension(1000, scrollSize.intValue());
        webDriver.manage().window().setSize(dimension);
        String html = "";
        //获取JS执行器,可以执行js代码来控制页面
        JavascriptExecutor driver_js= ((JavascriptExecutor) webDriver);
        //获取页面的高度
        Long scrollHeight = (Long) driver_js.executeScript("return document.body.scrollHeight");
        logger.info("article hight is : {}",scrollHeight);
        //因为要模拟鼠标滚动浏览网页的效果,所以设置了每次滚动的高度,然后通过整个页面的高度计算出股东的次数
        Long loopNum = scrollHeight/scrollSize;
        loopNum = loopNum+1;
        logger.info("page need scroll times : {}",loopNum);
        for(int i =0 ; i < loopNum; i++){
            Long start = i*scrollSize;
            Long end = (i+1)*scrollSize;
            //根据滚动的次数,依次滚动页面,为了图片能加载完全,停留1秒钟
            driver_js.executeScript("scroll("+start+","+end+")");
            Thread.sleep(1000);
        }
        //返回页面全部内容
        html = (String)driver_js.executeScript("return document.documentElement.outerHTML");
        webDriver.close();
        return html;
}

到此就可以完整的获取整个页面的内容了,但是这样获取原汁原味的内容,不一定能很好的展示在我们自己的页面上,下面是实际操作中遇到的两个问题。

解决过程中遇到的问题一

在展示抓取内容的过程中遇到图片跨域显示的或者防盗链的问题。刚开始的解决思路是通过Nginx反向代理的方式,来代理图片URL,但是因为源网页图片防盗链或者其他原因无法进行代理;
所以我用另外一种方式,通过将图片转换成Base64格式的内容替换原来的url来解决,这种方式确实可以解决这个问题但会引出新的问题,图片转换成Base64之后会将原网页内容变得非常大,在存储和展示过程中请求非常消耗存储空间和请求时间。
最后,我选择了将图片读取到本地,在通过Nginx代理的方式来处理。

		//获取网页图片元素
        List<WebElement> imgList = webDriver.findElements(By.tagName("img"));
//        data:image/gif;base64,
        for(WebElement img : imgList){
            String imgSrc = img.getAttribute("src");
            logger.info("img's src is : {}",imgSrc);
            String pattern = "^((http)|(https)).*";
            boolean imgSrcPattern = !StringUtils.isEmpty(imgSrc) && Pattern.matches(pattern, imgSrc);
            if(imgSrcPattern){
//                String strNetImageToBase64 = changImgUrlToBase64(imgSrc);
//                driver_js.executeScript("arguments[0].setAttribute(arguments[1],arguments[2])", img, "src", "data:image/png;base64,"+strNetImageToBase64);
                String imgUrlToImgFile = changImgUrlToImgFile(imgSrc);
				//通过JS来替换页面图片
				driver_js.executeScript("arguments[0].setAttribute(arguments[1],arguments[2])", img, "src", imgUrlToImgFile);
            }
        }

关键代码: driver_js.executeScript("arguments[0].setAttribute(arguments[1],arguments[2])", img, "src", imgUrlToImgFile);

将图片转换成Base64的代码

	private String changImgUrlToBase64(String imgSrc){
        String strNetImageToBase64 = "";
        try {
            URL imgURL = new URL(imgSrc);
            final HttpURLConnection conn = (HttpURLConnection) imgURL.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(3000);
            InputStream is = conn.getInputStream();
            ByteArrayOutputStream data = new ByteArrayOutputStream();
            // 将内容读取内存中
            final byte[] by = new byte[1024];
            int len = -1;
            while ((len = is.read(by)) != -1) {
                data.write(by, 0, len);
            }
            // 对字节数组Base64编码
            BASE64Encoder encoder = new BASE64Encoder();
            strNetImageToBase64 = encoder.encode(data.toByteArray());
            // 关闭流
            is.close();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return strNetImageToBase64;
    }

将图片下载到本地的代码

	private static String changImgUrlToImgFile(String imgSrc){
        String suffix = ".png";
        if(imgSrc.indexOf("gif") != -1){
            suffix = ".gif";
        }else if(imgSrc.indexOf("jpg") != -1){
            suffix = ".jpg";
        }else if(imgSrc.indexOf("jpeg") != -1){
            suffix = ".jpeg";
        }else if(imgSrc.indexOf("png") != -1){
            suffix = ".png";
        }
        String dir = "E:/lingy/asmImg/";
        String fileName = System.currentTimeMillis()+suffix;
        try {
            URL imgURL = new URL(imgSrc);
            final HttpURLConnection conn = (HttpURLConnection) imgURL.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(3000);
            InputStream is = conn.getInputStream();
            File imgFile = new File(dir+fileName);
            if(!imgFile.exists()){
                imgFile.createNewFile();
            }
//            FileOutputStream fos = new FileOutputStream(imgFile);
//            final byte[] by = new byte[1024];
//            int len = -1;
//            while ((len = is.read(by)) != -1) {
//                fos.write(by, 0, len);
//            }
//            is.close();
//            FileUtils.copyURLToFile(imgURL,imgFile);
            FileUtils.copyInputStreamToFile(is,imgFile);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (ProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        String imagRootPath = "http://localhost/asmImg/";
        return imagRootPath + fileName;
    }

解决过程中遇到的问题二

在抓取页面过程中,由于需要模拟网页滚动浏览的效果整个抓取非常耗时(页面越长越耗时),在前端对响应时间有要求的情况下,建议大家讲抓取页面的过程做成异步的。

总结

Selenium更强大的用处是进行自动化测试,模拟浏览器行为,操作页面元素等;自动化测试领域更加专业,这里不做深入的探讨,只是简单的介绍一下如何动态抓取一个的过程。其中的关键是获取页面元素执行JS脚本,主要使用webDriver.findElements()driver_js.executeScript()两个方法。由于上边的介绍只是一个简单的尝试,其他场景中会遇到更复杂的情况,比如说有的页面需要点击一下才能展示全部内容,这就需要调用js去触发。
以上就是本次解决问题的一个回顾,希望能给大家带来一定的思路和帮助。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值