Java基于Selenium动态抓取页面

2023-11-16

前情介绍

前段时间开发了一个功能,通过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去触发。
以上就是本次解决问题的一个回顾,希望能给大家带来一定的思路和帮助。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java基于Selenium动态抓取页面 的相关文章

随机推荐

  • VS code-设置问题

    起因 VS code本来是默认支持禁用非活动区域代码着色的 但我不知道怎么点取消了 今天又设置回来 因为感觉这样看代码方便点 方法 如果设置项旁边还有灰色小字提示与工作区不同 需要点一下将工作区设置也勾上 我就是只设置了用户区 没设置工作区
  • 搭配 umijs+vue的项目实战 以umijs为主应用+vue微应用

    这里写目录标题 搭配 umijs vue的项目实战 以umijs为主应用 vue微应用 umi js配置qiankun Vue2 x微应用 搭配 umijs vue的项目实战 以umijs为主应用 vue微应用 1 首先我们需要在项目中下载
  • 软件ETest

    ETest简介 ETest是一款软件开发环境IDE 基于该IDE可以完成嵌入式系统测试软件的开发与部署 该产品是由凯云科技率先在行业内推出的国产自主可控半实物仿真测试开发平台 有效打破了国内该领域长期由进口软件LabView DSpace等
  • 冲牙器使用记录

    保护好牙齿才能品尝美食 可以买一款比较便宜的冲牙器先试试 价位在100 200即可 一般有高中低三档 先用最低档位体验2个星期 冲的时候 喷嘴就贴着牙齿 不然会水花四溅
  • 51汇编——矩阵键盘

    矩阵按键可以说是51单片机一个比较典型的输入型的外设 它可以让人与单片机更好的进行交互 这一小节打算写一个4X4的矩阵按键 至于2X8 3X4 3X3 这些类型的其实他们的原理都是一样的 可以仿4X4的来写 矩阵按键扫描原理 这里使用的是8
  • 华为OD七日集训第5期 - 按算法分类,由易到难,循序渐进,玩转OD

    目录 一 适合人群 二 本期训练时间 三 如何参加 四 7日集训第5期 五 精心挑选21道高频100分经典题目 作为入门 第1天 逻辑分析 第2天 双指针 第3天 滑动窗口 第4天 贪心算法 第5天 二分查找 第6天 分治递归 第7天 搜索
  • 关于AI和ChatGPT的使用,AI编程(AIGC),AI绘画(3)

    使用AI绘画要注意哪些问题 1 版权和知识产权 使用别人的AI模型进行绘画可能会侵犯其版权和知识产权 需遵守相关法律法规 2 数据隐私 在使用AI绘画应用时 可能需要提供个人图像或图片等数据 要谨慎保护数据隐私 防止被滥用 3 算法可解释性
  • 移动端适配方案的优缺点比较

    当我们说到适配方案的时候越来越多的人会潜意识的翻译成移动端适配方案 确实是这样 在移动端蓬勃发展的今天 移动端的适配显得尤为重要 PC应用的适配已经不是适配方案主要需要考虑的了 随着移动互联网的来临 追求移动端的完美展示才是王道 最近也在做
  • 什么是三目运算符?三目运算符怎么使用?

    1 什么是三目运算符 三目运算符又称为 三元运算符 和 条件运算符 在java C C python JavaScript PHP等编程语言中都有三目运算符 三目运算符的作用就是判断 可以理解为if条件判断的简化版 2 三目运算符的运算规则
  • SQL数据分析概念与基础命令

    Parch Posey 数据库 实体关系图 实体关系图 ERD 是查看数据库中数据的常用方式 下面是我们将用于 Parch Posey 数据库的 ERD 这些图可帮助你可视化正在分析的数据 包括 表的名称 每个表中的列 表配合工作的方式 你
  • 蓝色巨人——IBM公司

    蓝色巨人 IBM之所以被称为蓝色巨人是因为他蓝色的徽标 还有IBM的深蓝超级计算机在第二次人机大战中胜出 IBM是为数不多的在成功逃过数次经济危机 并在历次技术革命中成功转型的公司之一 虽然他是大型计算机制作商 但是他已经过气了 不过他确是
  • 搞懂Vision Transformer 原理和代码,看这篇技术综述就够了(三)

    点击蓝字 关注极市平台 作者丨科技猛兽 来源丨极市平台 审核丨邓富城 极市导读 本文为详细解读Vision Transformer的第三篇 主要解读了两篇关于Transformer在识别任务上的演进的文章 DeiT与VT 它们的共同特点是避
  • 代码查看工具_F12 - 开发者工具详解

    学习使用浏览器自带的 F12 网页开发者工具 可以帮助前端以及测试人员来快速定位调试分析问题 解决问题 一 如何调出开发者工具 在浏览器页面上 F12 键 笔记本电脑 Fn F12 右键选择 检查 N 快捷键 Ctrl Shift i 二
  • Qt 信号和槽的机制(逻辑清晰的来说清楚信号和槽,呕心沥血之作)

    Qt 信号和槽的机制 首先说声对不起 上次在PyQt5中写信号与槽 由于时间原因没有写完 有小伙伴留言说 希望把这章补全 所以 这是一篇迟来的文章 再次向大家说声抱歉 一 桌面程序的结构 Qt的使用场景 主要是应用于桌面程序来使用 不管你使
  • SpringCloud+mybatis+WeMagic Mapper注入失败 NPE空指针异常

    项目背景 Springcloud mybatis webMagic 获取百度热搜榜 搜狗热搜榜等热搜数据并存储到数据库中 使用Mybatis Generator自动生成Mapper后放置在Mapper文件夹 并添加了对应的注解支持 serv
  • 深度学习系列50:苹果m1芯片加速pytorch

    1 介绍 Apple的Metal Performance Shaders MPS 作为PyTorch的后端来加速GPU训练 MPS后端扩展了PyTorch框架 提供了在Mac上设置和运行操作的脚本和功能 MPS通过针对每个Metal GPU
  • 树莓派gpio接ttl转usb串口调试

    树莓派设置修改 以下教程只在树莓派3B 验证测试通过 其它版本未经测试仅供参考 1 gt 修改config txt enable uart 1 找到这行 将值改为1 dtoverlay pi3 miniuart bt 在config txt
  • uni-apph5 端获取当前位置坐标及地理位置逆解析

    1 uni app getLocation在浏览器端获取的地理位置坐标是你电脑里面ip地址位置的坐标 2 调用百度地图api逆解析地址对坐标解析详细地址 代码如下 经纬度 记得改成活的 测试用写死了 uni getLocation type
  • 详解 MySQL InnoDB 实现原理

    MySQL InnoDB 引擎现在广为使用 它提供了事务 行锁 日志等一系列特性 本文分析下 InnoDB 的内部实现机制 MySQL 版本为 5 7 24 操作系统为 Debian 9 1 InnoDB 架构 Innodb 架构图 Inn
  • Java基于Selenium动态抓取页面

    Java基于Selenium动态抓取页面 前情介绍 解决 思路 编码 解决过程中遇到的问题一 解决过程中遇到的问题二 总结 前情介绍 前段时间开发了一个功能 通过HttpClient访问某个页面 获取页面的全部html内容 之后通过抓取过来