selenium
文章目录
seleniumSelenium 是什么?下载驱动参数大全工具类(简化复杂操作)常用方法请求定位标签获取内容判断行为窗口键盘和鼠标结束js执行表单的常用操作选择下拉框元素单选和复选表单提交 其他操作移动滚动条 案例爬取某bi图片爬取某bi评论
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图片
实现思路
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评论
实现思路
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); } } }
效果图: