Java-Selenium自动化教程(学了不亏)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

selenium

文章目录

Selenium 是什么

Selenium 是一组软件工具集,每一个都有不同的方法来支持测试自动化。大多数使用 Selenium 的QA工程师只关注一两个最能满足他们的项目需求的工具上。然而学习所有的工具你将有更多选择来解决不同类型的测试自动化问题。这一整套工具具备丰富的测试功能很好的契合了测试各种类型的网站应用的需要。这些操作非常灵活有多种选择来定位 UI 元素同时将预期的测试结果和实际的行为进行比较。Selenium 最关键的特性是支持在多浏览器平台上进行测试。Chrome和Firefox相继推出了无头浏览器模式由于这些大厂的加入phantomjs的用户量渐渐减少新版的selenium已经不再支持phantomjs

selenium - java这方面几乎很少,大部分都不全而且很乱,导致后来人没法学习, 这就是生态的重要性, 那么我就来刨根问底将Java Selenium 的东西都整理出来, 其实Java爬虫很方便的多看源码其实啥都告你了

小提示: 在java中selenium和python的selenium一样,所以可以参考python的文档,前提你需要能看懂python代码 ^_^

下载驱动

Chromedriver下载地址
http://chromedriver.storage.googleapis.com/index.html
http://npm.taobao.org/mirrors/chromedriver/
两个地址都可以下载根据自己的chrome浏览器的版本选择下载即可(一定要和自己游览器版本一致,否则没法使用)
查看浏览器版本 ↓
在这里插入图片描述
在这里插入图片描述
没找到103.0.5060.114那么下载你当前游览器版本的临近最新版就行
在这里插入图片描述
在这里插入图片描述
下载解压后把exe文件保存好,用于之后程序中调用

        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
<!--            目前来说就3.141.59这个版本好使高版本会有问题-->
            <version>3.141.59</version>
        </dependency>

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>22.0</version>
        </dependency>

参数大全

https://peter.sh/experiments/chromium-command-line-switches/

工具类(简化复杂操作)

  • 最优初Selenium 始化
  • 代理
  • 显示等待
  • 强制等待
  • 隐式默认等待
  • 截图
  • 支持多线程并发
package com.reptile;

import com.file.ReadWriteFileUtils;
import lombok.SneakyThrows;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 简要描述
 *
 * @Author: huanmin
 * @Date: 2022/7/18 17:27
 * @Version: 1.0
 * @Description: 文件作用详细描述....
 */
public class ChromeDriverUtil {
    //文件版本,防止多线程缓存文件和用户文件共享,导致创建错误
    private  static  AtomicInteger fileSerial=new AtomicInteger(0);
    private ChromeDriver driver;

    public ChromeDriverUtil(String path, boolean pd, boolean img) {
        init(path, pd, img);
    }

    @SneakyThrows
    private void init(String path, boolean pd, boolean img) {
        System.setProperty("webdriver.chrome.driver", path);
        ChromeOptions options = new ChromeOptions();
        if (!pd) {
            options.addArguments("--headless"); //无浏览器模式
        }
        options.addArguments("--disable-gpu"); // 谷歌文档提到需要加上这个属性来规避bug
        options.addArguments("--disable-software-rasterizer"); //禁用3D软件光栅化器
        options.addArguments("--no-sandbox");// 为了让linux root用户也能执行
        // 优化参数
        options.addArguments("--disable-dev-shm-usage"); //解决在某些VM环境中/dev/shm分区太小导致Chrome失败或崩溃
        if (img) {
            options.addArguments("blink-settings=imagesEnabled=false"); //禁止加图片,如果爬取图片的话,这个不能禁用
            options.addArguments("--disable-images");
        }

        String tmpdir = System.getProperty("java.io.tmpdir");
        String dir = tmpdir + File.separator + "chrome_file_data_cache"+File.separator+fileSerial.incrementAndGet();
        File file1 = new File(dir+File.separator + "data");
        if(file1.exists()){
            file1.mkdirs();
        }
        File file2 = new File(dir+File.separator + "cache");
        if(file2.exists()){
            file1.mkdirs();
        }

        options.addArguments("--user-data-dir=" + file1.getAbsolutePath()); //解决打开页面出现data;空白页面情况,因为没有缓存目录
        options.addArguments("--disk-cache-dir=" + file2.getAbsolutePath()); //指定Cache路径
        options.addArguments("--incognito") ; //无痕模式
        options.addArguments("--disable-plugins"); //禁用插件,加快速度
        options.addArguments("--disable-extensions"); //禁用扩展
        options.addArguments("--disable-popup-blocking"); //关闭弹窗拦截
        options.addArguments("--ignore-certificate-errors"); //  禁现窗口最大化
        options.addArguments("--allow-running-insecure-content");  //关闭https提示 32位
        options.addArguments("--disable-infobars");  //禁用浏览器正在被自动化程序控制的提示  ,但是高版本不生效

        if (!pd) {
            //无浏览器模式-最大化窗口  ,防止有些元素被隐藏
            int screenWidth = ((int) java.awt.Toolkit.getDefaultToolkit().getScreenSize().width);
            int screenHeight = ((int) java.awt.Toolkit.getDefaultToolkit().getScreenSize().height);
            options.addArguments("window-size=" + screenWidth + "," + screenHeight);
        }
        //随机设置请求头
        options.addArguments("--user-agent=" + UserAgent.getUserAgentWindows());
        proxy(options, false); //设置代理 ,true 开启代理
        driver = new ChromeDriver(options);//实例化
        if (pd) {
            driver.manage().window().maximize(); //界面的方式, 最大化窗口, 防止有些元素被隐藏,无界面就不要使用了
        }
        //当我们去定位页面元素时如果元素没有找到不会立即抛出异常而是周期性地通常为 0.5s去重新寻找直到该元素找到或者超过最大等待时间才结束 ,超时后就报错NoTouchElementException
        //当我们使用implicitly_wait()时如果想要定位的元素已经找到但是它的内容如文本内容属性等没有加载出来此时隐式等待无效仍会直接抛出NoSuchElementException异常这也是为什么我们很多时候仍需要使用time.sleep()的原因。
        driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);

    }

    //无头模式,不加载图片
    public static ChromeDriverUtil buildHide(String path) {
        return new ChromeDriverUtil(path, false, true);
    }

    //无头模式,加载图片
    public static ChromeDriverUtil buildHideImg(String path) {
        return new ChromeDriverUtil(path, false, false);
    }

    //显示游览器 ,全功能
    public static ChromeDriverUtil build(String path) {
        return new ChromeDriverUtil(path, true, false);
    }

    public ChromeDriver getDriver() {
        return driver;
    }

    //强制等待 代码在执行到某个位置时强制等待一段时间
    @SneakyThrows
    public void sleep(long ms) {
        Thread.sleep(ms);
    }

    // 显示等待,是为了解决隐式等待遗留的问题,比如元素显示了,但是内部的文本没有显示出来,可能文本是通过ajax异步的会比较慢
    public WebElement wait(int seconds, ExpectedCondition<WebElement> expectedCondition) {
        WebDriverWait webDriverWait = new WebDriverWait(driver, seconds);
        //返回null或者false,等待500毫秒继续尝试,直到过期
        WebElement until = webDriverWait.until(expectedCondition);

        return until;
    }

    //自行扩展, 从接口中读取,或者从文件中读取都行
    private void proxy(ChromeOptions options, boolean pd) {
        if (pd) {
            String prox = "101.200.127.149:" + 3129;
            Proxy p = new Proxy();
            p.setHttpProxy(prox);//http
//        p.setFtpProxy(prox); //ftp
//        p.setSslProxy(prox);//ssl
//        p.setSocksProxy(prox); //SOCKS
//        p.setSocksUsername("");
//        p.setSocksPassword("");

            options.setProxy(p);
        }
    }

    //截图
    public void screenshotPNG(TakesScreenshot takesScreenshot, File file) {
        byte[] screenshotAs1 = takesScreenshot.getScreenshotAs(OutputType.BYTES);
        ReadWriteFileUtils.writeByte(screenshotAs1, file);
        try (
                FileOutputStream fos1 = new FileOutputStream(file);
                BufferedOutputStream fos = new BufferedOutputStream(fos1);
        ) {
            fos.write(screenshotAs1, 0, screenshotAs1.length); // 写入数据
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}


上面代码UserAgent.getUserAgentWindows() 可以参考Java-JSONP(爬虫) 这个里面都有

常用方法

请求

Navigation navigate()
在这里插入图片描述

driver.get(url); 请求一个页面,不支持前进和后退切换
driver.navigate().to(url); 和get类似,支持前进和后退切换
driver.navigate().back(); 退到上一个页面 ,前提必须前进了一个页面才能回退
driver.navigate().forward(); 指前进到下一个页面 ,前提是必须后退后才能前进
driver.navigate().refresh(); 刷新当前页面

定位标签

ChromeDriver 全部选择器都有, WebElement只有两个通用选择器
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

WebElement findElement(By by); 通用搜索,第一个 , By里包含常用的各种搜索
List<WebElement> findElements(By by); 通用搜索,多个,By里包含常用的各种搜索
WebElement findElementById(String using) 查询指定id的标签
WebElement findElementByLinkText(String using) 查询a标签内容是using的 ,第一个
List<WebElement> findElementsByLinkText(String using) 查询a标签内容是using的 ,多个
WebElement findElementByPartialLinkText(String using) 查询a标签内容是using的 , 模糊匹配 ,第一个
List findElementsByPartialLinkText(String using) 查询a标签内容是using的 , 模糊匹配 ,多个
WebElement findElementByTagName(String using) 查询标签名称 ,第一个
List<WebElement> findElementsByTagName(String using) 查询标签名称,多个
WebElement findElementByName(String using) 查询标签属性name,第一个
List<WebElement> findElementsByName(String using) 查询标签属性name,多个
WebElement findElementByClassName(String using) 查询标签数据class, 第一个
List findElementsByClassName(String using) 查询标签数据class, 多个
WebElement findElementByCssSelector(String using) 使用css选择器 , 第一个
List findElementsByCssSelector(String using) 使用css选择器 , 多个
WebElement findElementByXPath(String using) 使用 XPath 选择器 , 第一个
List findElementsByXPath(String using) 使用 XPath 选择器 , 多个

获取内容

在这里插入图片描述

String getPageSource() 获取页面html
String getTitle() 获取页面标题
String getText() 获取此元素包括子元素的可见即未被CSS隐藏文本。
String getTagName(); 获取此元素的标签名
String getAttribute(String name); 获取元素指定属性的值
Point getLocation(); 获取当前元素,基于页面左上角的为准
Dimension getSize(); 渲染元素的宽度和高度是多少
Rectangle getRect(); 渲染元素的位置和大小
String getCssValue(String propertyName); 获取指定元素的CSS属性的值
String getCurrentUrl(); 获取表示浏览器正在查看的当前URL的字符串。

判断

boolean isEnabled(); 输入元素当前是否已启用对于输入元素之外的所有元素这通常都将返回true ,如果元素已启用则为True否则为false。
boolean isSelected(); 确定是否选择了此元素。此操作仅适用于输入元素如复选框、选择中的选项和单选按钮 ,如果当前选择或选中了元素则为True否则为false。
boolean isDisplayed(); 元素是否显示

行为

void clear(); 如果该元素是文本输入元素则会清除该值
void submit(); 提交from表单
void click(); 单击此元素 ,单击元素有一些先决条件。元素必须可见并且其高度和宽度必须大于0。
void sendKeys(CharSequence… keysToSend); 使用此方法模拟在元素中键入可以设置其值。

注意:

有些网站的页面是使用动态加载js的,这也就会导致html页面出来了,但是js还没执行完毕,相关事件还没绑定到具体的元素上,那么虽然选择器能找到元素,但是进行事件操作就是不好使的情况 ,我们可以这样解决在加载页面时候前进行强制等待几秒等待全部加载完毕后在进行后续操作

有些网站的指定元素的事件不是在页面加载的时候加载,而是当鼠标悬浮上指定的元素后在动态绑定的, 我们可以控制鼠标悬浮上去停一下,之后在操作这个元素

有些网站的部分元素的事件是根据可视化窗口来动态加载js的, 所以我们操作不在可视化窗口内的元素, 就需要滑动滚动条让元素显示在可视化窗口内部才行

窗口

在这里插入图片描述

driver.manage().window()

在这里插入图片描述

在某些时候有些网站在执行的时候可能会打开另外一个窗口或者多个窗口这个时候如果我们想要回到原先的窗口获取其他指定的窗口应该怎么办呢WebDriver.TargetLocator targetLocator = driver.switchTo(); 可以通过TargetLocator来切换窗口
在这里插入图片描述

String getWindowHandle(); 返回当前窗口句柄,通过将其传递给switchTo进行切换窗口
driver.switchTo().window(windowHandle); 切换到指定句柄的窗口
WebDriver frame(int index); 切换窗口 ,从0开始 ,一旦切换完成后续调用都会对该窗口进行。
WebDriver frame(String nameOrId); 切换窗口 nameOrId–帧窗口的名称、元素的id或从零开始的索引
WebDriver frame(WebElement frameElement); 使用先前定位的WebElement选择框架。
WebDriver parentFrame(); 将焦点更改为父窗口
WebDriver defaultContent(); 选择页面上的第一个框架或者当页面包含iFrame时选择主文档。
WebElement activeElement(); 换到当前的文档中具有焦点的元素如果无法检测到则切换到正文元素则为body元素。
Alert alert(); 切换到此特定驱动程序实例的当前活动模式对话框。

键盘和鼠标

一、键盘事件

ctrl+a : driver.findElement(By.id(“kw”)).sendKeys(Keys.CONTROL, “a”);
ctrl+x: driver.findElement(By.id(“kw”)).sendKeys(Keys.CONTROL, “x”);
ctrl+c: driver.findElement(By.id(“kw”)).sendKeys(Keys.CONTROL, “c”);
ctrl+v: driver.findElement(By.id(“kw”)).sendKeys(Keys.CONTROL, “v”);
F键操作: driver.findElement(By.id(“kw”)).sendKeys(Keys.F5);
TAB键: driver.findElement(By.id(“kw”)).sendKeys(Keys.TAB);
回车键: driver.findElement(By.id(“kw”)).sendKeys(Keys.ENTER);
空格键: driver.findElement(By.id(“kw”)).sendKeys(Keys.SPACE);
还有其他键盘的操作在这里只列举常用的键位。

在这里插入图片描述

二、鼠标事件

  • Actions actions = new Actions(driver);
  • perform() 执行动作

右键点击enement的元素
actions.contextClick(element).perform();

左键单击 enement元素
actions.clickAndHold(element).perform();

鼠标左键双击 enement元素
actions.doubleClick(element).perform();

鼠标悬停enement元素
actions.moveToElement(element).perform(); //中间
actions.moveToElement(element,x,y).perform(); //指定位置

将鼠标从其当前位置或0,0 移动鼠标 ,如果提供的坐标在视口之外鼠标将在浏览器窗口之外结束则会滚动视口以匹配。
actions.moveToElement(x,y).perform();

拖动元素, 在源元素的位置执行点击并保持移动到目标元素的位置然后释放鼠标
Actions dragAndDrop(WebElement source, WebElement target)
Actions dragAndDropBy(WebElement source, int xOffset, int yOffset) 拖动到指定位置

结束

driver.close(); 关闭当前窗口如果它是当前打开的最后一个窗口则退出浏览器。
driver.quit(); 退出此驱动程序关闭每个相关窗口。

注意: 在操作完毕后必须调用quit() 进行释放资源,否则驱动将长存在内存中不会被释放掉,通过任务管理器就能看到一大堆的chromedriver.exe
在这里插入图片描述

js执行

某些时候我们可能通过getText()的方式获取标签的文本值并不会生效 ,但是我们可以通过写js语句来解决大部分问题。 执行js语句Object executeScript(String script, Object... args); 该方法可以供我们执行js语句script代表我们的js语句args代表传给script的值接受参数使用arguments[0]…[1]…[2].依次来接受。示例如下
假设我们想要获取某个标签的文本值

第一种方式driver.executeScript("return document.getElementById('blogClick').innerText ;")

第二种方式

WebElement blogClick = driver.findElementById("blogClick");
driver.executeScript("return arguments[0].innerText;",blogClick);

结果需要返回值的话那么需要指定return

  • 对于HTML元素返回WebElement
  • 对于十进制返回双精度
  • 对于非十进制数返回Long,
  • 对于布尔值返回布尔值
  • 对于所有其他情况返回一个字符串
  • 对于数组返回一个列表 ,支持嵌套列表。
  • 对于对象返回一个map
    遵循上述规则。 除非值为null或没有返回值否则返回null 如果参数不符合这些条件将引发异常。

表单的常用操作

选择下拉框元素

Select select = new Select(driver.findElementById("select")); 
//通过索引选择 
select.selectByIndex(1);
//通过value值获取 
select.selectByValue("zhangsan")
//通过文本值获取
select.selectByVisibleText("张三");

单选和复选

driver.findElementById("radio").click(); //单选按钮

复选框其实和单选按钮一样都是定位元素点击元素在选择元素之前我们可以通过isSelected()来判断元素是否被选择isEnabled()来判断元素是否被禁用。

表单提交

WebElement form = driver.findElementById("form");
//只能用于表单提交
form.submit();

在某些时候有些网站在执行的时候可能会打开另外一个窗口这个时候如果我们想要回到原先的窗口应该怎么办呢

//获取窗口的句柄 
String windowHandle = driver.getWindowHandle(); 
//另外一个窗口执行... 
//另外一个窗口执行结束后我们可以通过switchTo()去返回到原先窗口 
driver.switchTo().window(windowHandle);

其他操作

移动滚动条

左右上下滑动指定元素的滚动条
driver.executeScript("document.getElementById('arguments[0]').scrollTop=arguments[1];","agreementMain",1200);
driver.executeScript("document.getElementById('arguments[0]').scrollLeft=arguments[1];","agreementMain",1200);

左右上下滑动window窗体的滚动条
driver.executeScript("window.scrollTo(arguments[0],arguments[1]);",0,1200);

网页正文全文宽 document.body.scrollWidth
网页正文全文高 document.body.scrollHeight

获取元素距当前可视区域顶部的距离

var box=document.getElementById(‘box’); // 获取元素
alert(box.getBoundingClientRect().top); // 元素上边距离页面上边的距离
alert(box.getBoundingClientRect().right); // 元素右边距离页面左边的距离
alert(box.getBoundingClientRect().bottom); // 元素下边距离页面上边的距离
alert(box.getBoundingClientRect().left); // 元素左边距离页面左边的距离

有些网站的元素他的事件相关js,是根据可视化窗口来,当元素在可视化窗口内,就会调用对应的js绑定事件,那么我们可以这样

		
        //找到元素,以渲染完毕
        WebElement elementa = driver.findElement(By.cssSelector(".timeline-toggle-btn .timeline-icon-toggle-down"));
        // 移动滚动条将,对应的元素显示,在可视化窗口中
        driver.executeScript("window.scrollTo(0,arguments[0].getBoundingClientRect().top-100)",elementa);
        //展开下拉列表
        elementa.click();

有些列表内的元素是懒加载,如果我们直接将滚动条滑动到底部,元素是不会加载出来的,触发懒加载需要时间的,那么我们需要模拟人为滑动滚动条的方式来触发列表中所有元素让他们进行加载

        //获取需要滚动的距离
        Long o =(Long) driver.executeScript("let elementsByClassNameElement = document.querySelector(arguments[0]);\n" +
                        " return elementsByClassNameElement.offsetHeight+elementsByClassNameElement.offsetTop;",
                ".timeline-box.clearfix");
        //获取当前滚动条位置
        Long o1 =(Long) driver.executeScript("return document.documentElement.scrollTop;");
        // 向下移动滚动条让列表的内容懒加载出来
        for (Long i = o1; i <o ; i+=500) {
            driver.executeScript("window.scrollTo(0,arguments[0]);",i);
            Thread.sleep(50);
        }

有些页面内容是根据滚动条ajax动态加载的,那么我们可以将滚动条拉倒底部将所有内容都有加载出来

        //移动滚动条,到底部
        Long len=(Long)   driver.executeScript("return document.body.scrollHeight;");
        while (true){
            //移动滚动条
            driver.executeScript(" window.scrollTo(0,arguments[0])",len);
            build.sleep(200);
            Long len1=(Long)   driver.executeScript("return document.body.scrollHeight;");
            if(Objects.equals(len, len1)){
                break;
            }
            len=len1;
        }

案例

某 bi 在反爬虫界做的非常不错,可以说是数一数二的了, 那么我们就在某bi中进行案例演示

爬取某bi图片

在这里插入图片描述
实现思路

  1. 将展开按钮显示在可视化窗口里 (js 动态加载)
  2. 然后触发点击事件,让列表展开
  3. 慢慢的滑动滚动条,让没有显示的列表都预加载出来 (js 动态加载)
  4. 之后定位到列表的父级
  5. 通过附件遍历全部子集
  6. 然后在每个子集中找到,img标签取出src里面的图片连接
    public static void main(String[] args) throws IOException, InterruptedException {
        ChromeDriverUtil build = ChromeDriverUtil.build("D:\\常用资源\\chromedriver.exe");
        ChromeDriver driver = build.getDriver();
        driver.navigate().to("https://www.bilibili.com/anime");

        //找到元素,以渲染完毕
        WebElement elementa = driver.findElement(By.cssSelector(".timeline-toggle-btn .timeline-icon-toggle-down"));
        //移动到需要点击的元素位置 , 他这网站的事件是动态绑定的,也就是懒加载形式,元素出现在可视化窗口后对应的js相关的事件才会绑定上去
        driver.executeScript("window.scrollTo(0,arguments[0].getBoundingClientRect().top-300);",elementa);
        //给js留点时间绑定到元素上
        Thread.sleep(100);
         //展开下拉列表
        elementa.click();
        //获取需要滚动的距离
        Long o =(Long) driver.executeScript("let elementsByClassNameElement = document.querySelector(arguments[0]);\n" +
                        " return elementsByClassNameElement.offsetHeight+elementsByClassNameElement.offsetTop;",
                ".timeline-box.clearfix");
        //获取当前滚动条位置
        Long o1 =(Long) driver.executeScript("return document.documentElement.scrollTop;");
        // 向下移动滚动条让列表的内容懒加载出来
        for (Long i = o1; i <o ; i+=500) {
            driver.executeScript("window.scrollTo(0,arguments[0]);",i);
            Thread.sleep(50);
        }

        WebElement element1 = driver.findElement(By.cssSelector(".timeline-box.clearfix"));
        List<WebElement> elements = element1.findElements(By.className("timeline-item"));
        for (WebElement webElement : elements) {
            //如果没有显示那么10秒内,每间隔500毫秒重新获取一次
            WebElement wait1 = build.wait(10, (driver1) -> {
                WebElement element = webElement.findElement(By.cssSelector(".common-lazy-img img"));
                if(element.getAttribute("src").isEmpty()){
                    return null; //如果值为空那么,继续查询
                }
                return  element;
            });
            System.out.println(wait1.getAttribute("src"));
        }
        driver.quit();
    }

效果如下:

Java-Selenium-爬取某bi图片

爬取某bi评论

在这里插入图片描述
实现思路

  1. 找到对应的评论区父级
  2. 滑动滚动条将所有评论区都展开 (破解懒加载机制)
  3. 然后找到每一个评论项
  4. 我们可以看到这个评论区,是二级评论区,那么先获取一级评论然后在获取一级评论的回复评论
 public static void main(String[] args) {
        ChromeDriverUtil build = ChromeDriverUtil.build("D:\\常用资源\\chromedriver.exe");
        ChromeDriver driver = build.getDriver();
        Map<String,List<String>> map=new LinkedHashMap<>();
        try {
            driver.navigate().to("https://www.bilibili.com/bangumi/play/ep541073?spm_id_from=333.1007.partition_recommend.content.click");

            WebElement  element=build.wait(10,(driver1)->{
                WebElement element1=null;
                try {
                   element1 = driver.findElement(By.cssSelector(".comm .bb-comment .comment-list"));
                } catch (Exception e) {
                    return element1; //获取失败了,那么重新获取
                }
                return element1;
            });


            //移动滚动条,到底部
            Long len = (Long) driver.executeScript("return document.body.scrollHeight;");
            while (true) {
                //移动滚动条
                driver.executeScript(" window.scrollTo(0,arguments[0])", len);
                build.sleep(200);
                Long len1 = (Long) driver.executeScript("return document.body.scrollHeight;");
                if (Objects.equals(len, len1)) {
                    break;
                }
                len = len1;
            }

            List<WebElement> elements = element.findElements(By.xpath("//div[@mr-show]"));
            for (WebElement webElement : elements) {
                WebElement element1 = webElement.findElement(By.cssSelector(".con .text"));
                map.put(element1.getText(), new ArrayList<>());
                WebElement element2 = webElement.findElement(By.cssSelector(".con .reply-box"));
                if(!element2.getText().isEmpty()){
                    List<WebElement> elements1 = element2.findElements(By.className("reply-item"));
                    List<String> list = map.get(element1.getText());
                    list.addAll(elements1.stream().map(data->data.findElement(By.cssSelector(".user .text-con")).getText()).collect(Collectors.toList()));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            driver.quit(); //无论啥情况必须释放资源
        }

        //遍历数据
        for (Map.Entry<String, List<String>> stringListEntry : map.entrySet()) {
            System.out.println(stringListEntry);
            for (String s : stringListEntry.getValue()) {
                System.out.println("--:"+s);
            }

        }

    }

效果图:
在这里插入图片描述

在这里插入图片描述

点赞 -收藏-关注-便于以后复习和收到最新内容
有其他问题在评论区讨论-或者私信我-收到会在第一时间回复
在本博客学习的技术不得以任何方式直接或者间接的从事违反中华人民共和国法律,内容仅供学习、交流与参考
免责声明本文部分素材来源于网络版权归原创者所有如存在文章/图片/音视频等使用不当的情况请随时私信联系我、以迅速采取适当措施避免给双方造成不必要的经济损失。
感谢,配合,希望我的努力对你有帮助^_^
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java