Bootstrap

nodejs操作selenium-webdriver

一、安装依赖

npm install selenium-webdriver

二、下载chormdriver 文件,并配置全局环境变量

注意:需下载和电脑安装的谷歌浏览器版本相匹配的

ChromeDriver官网下载地址:https://sites.google.com/chromium.org/driver/downloads

ChromeDriver官网最新版下载地址:https://googlechromelabs.github.io/chrome-for-testing/

ChromeDriver国内镜像下载地址:https://registry.npmmirror.com/binary.html?path=chromedriver/

ChromeDriver国内镜像最新版下载地址:https://registry.npmmirror.com/binary.html?path=chrome-for-testing/

三、具体使用方法总结

  • 基本配置---包括禁止浏览器弹出通知
const { Builder, By, Key, until, ActionChains } = require('selenium-webdriver')
const chrome = require('selenium-webdriver/chrome');

let options = new chrome.Options();
// 禁止浏览器弹出通知
options.addArguments('--disable-notifications');
let driver = await new Builder().forBrowser('chrome').setChromeOptions(options).build()
  • 打开访问网址
driver.get('https://www.instagram.com?locale=zh_CN')
  • 浏览器设置cookie
let cookie = { name:'key', value:'value' } driver.manage().addCookie(cookie)
  • 设置休眠时间、最大化浏览器窗口、刷新页面、获取页面源码
// 休眠时间 毫秒 
driver.sleep(1000); 
// 最大化浏览器窗口 
driver.manage().window().maximize(); 
// 刷新页面 
driver.navigate().refresh() 
// 获取页面源码 
driver.getPageSource()
  • 浏览器打开多个窗口,切换、关闭浏览器窗口
// 用来处理页面需要打开新页面操作情况,切换操作页面,关闭当前操作页面;
// 获取当前窗口句柄
let originalWindow = await driver.getWindowHandle();

// 获取了所有打开的窗口句柄
let allWindows = await driver.getAllWindowHandles();
for(let windowHandle of allWindows) {
   if(windowHandle !== originalWindow) {
       // 切换到窗口句柄
        await driver.switchTo().window(windowHandle);
   }
}

// 关闭单个窗口句柄
await driver.close()

//关闭所有页面
await driver.quit();
  • 查找元素的几种方法
1, 等待第一个查找元素类型出现,可以设置等待时间
注意: 在使用过程中需要 try{}catch(e){} 进行包裹,如果没有找到元素会报错,不再执行之后的代码片段
        此方法两个参数都为必填,如果第二个等待时间参数未填写,那么页面会一直等待,直到元素出现,这样就会导致页面出现错误时,未
        获取到元素,也无法抓取异常信息
await driver.wait(until.elementLocated(By.xpath("//a[contains(@href,'/followers/')]")), 10000)
拓展:By.xpath的基本语法总结

      1、By.xpath('//a[contains(@href,"/followers/")]') // 查找a标签属性值href中包含'/followers/'的元素
      2、By.xpath('//a[@href,"https://www.baidu.com"]') // 查找a标签属性值href为"https://www.baidi.com"
      3、By.xpath('//span[contains(text(),"更多")]')    // 查找文本内容包含"更多"的span标签
      4、By.xpath('//a[text()="更多内容"]') // 查找文本内容等于'更多内容'
      5、By.xpath('//a[@href="***" and text()="***"]') // 多条件查询
      6、By.xpath('..') // 父节点
      7、By.xpath('./following-sibling::*[1]') // 获取下一个兄弟节点
         By.xpath('./preceding-sibling::*[1]') // 获取上一个兄弟节点
      8、By.xpath('child::div') // 查找当前元素的所有直接子 div 元素 
         By.xpath("//input[@type='file'][@data-testid='fileInput']")  
          
      By.css的基本语法总结
      
      1、By.css('#username')  // 通过id获取元素
      2、By.css('.password')  // 通过类名选择元素
      3、By.css('button')     // 通过标签名选择元素
      4、By.css('a[href="/about"]') // 通过属性选择元素
      5、By.css('.acan.acao')   // 通过多个类名选择器选择元素
      6、By.css('form input[type="text"]') // 通过组合选择器选择元素
         By.css('ul > div:nth-child(3) > div:first-child > div:first-child')
         By.css("div[aria-label='发帖'][role='button']")
2,查找元素
    2.1 查找单个元素
    注意:使用过程中需要 try{}catch(e){} 进行包裹,如果没有找到元素会报错,不再执行之后的代码片段
    let divDom = await driver.findElement(By.xpath('//div[@data-id, "div"]'))
    // 查找divDom 元素下的特定元素
    let childrenDivDom = await divDom.findElement(By.xpath('.//span[contains(text(),"更多")]'))
    let 
    2.2 查找多个元素 
    注意:返回的内容是一个数组,如果没有找到元素不会报错,返回一个空数组,通过数组的长短来定位自己要找的元素位置
    await driver.findElements(By.css("button._acan._acao._acas._aj1-._ap30")) 
  • 元素点击
let settingNode = await driver.findElement(By.css('.settings-button'));
// 两种点击方法
// 第一种:适用于大多数常规情况,会触发所有浏览器的默认情况
// 优点:自然触发事件:会触发所有浏览器的默认事件,如 mousedown、mouseup、click 等。
//       简单易用:代码更简洁,更容易理解和调试。
// 缺点:限制性:如果元素被其他元素遮挡或不可见,可能会抛出 ElementNotInteractableError 异常。
//       依赖于元素状态:元素必须是可见且可交互的。
await settingNode.click() 
// 第二种 
// 优点: 兼容性:有些情况下,直接调用 settingNode.click(); 可能会失败,特别是当元素被其他元素遮挡或不可见时。使用 executeScript 可以绕过这些限制,强制执行点击操作。
//       灵活性:可以执行更复杂的 JavaScript 操作,而不仅仅是点击。
// 缺点: 不触发所有事件:通过 JavaScript 执行的点击操作可能不会触发所有浏览器的默认事件(如 mousedown、mouseup 等)。
//       调试困难:如果点击操作出现问题,调试起来可能会更复杂,因为它是通过 JavaScript 执行的。
await driver.executeScript("arguments[0].click();", settingNode);
  • 输入框、上传文件框输入内容
let destinationPath = "添加内容";
await uploadInput.sendKeys(destinationPath);
// 输入内容并且按下enter按钮
await uploadInput.sendKeys(code, Key.RETURN);
  • 获取文本
let fansEle = await driver.findElement(By.xpath("//div[@data-id='following']"));
await fansEle.getText()
  • 页面浏览器滚动或者某个区域滚动
// 主要用途:在获取页面信息时, 遇到含有滚动条的页面,需要滚动动态加载网页数据,抓取全部数据内容
// 控制网页滚动到底部
await driver.executeScript('window.scrollTo(0, document.body.scrollHeight)')
// 等待滚动完成
await driver.wait(until.elementLocated(By.css('body')), 1000);
// 获取 scrollTop 的值
let scrollTop = await driver.executeScript('return window.pageYOffset;');

// 针对某个元素内部的滚动
let ulElement = await commonBox.findElement(By.css('ul'));
// 控制ulElement 区域
await driver.executeScript(`arguments[0].scrollTop = arguments[0].scrollHeight;`,ulElement);
// 获取页面当前滚动的距离
let scrollTop = await driver.executeScript(`return arguments[0].scrollTop`, ulElement);
  • 模拟鼠标悬浮
// 主要用途:网页中有些元素是隐藏的,比如时间、消息发送人等,需要鼠标悬浮到指定元素之后,才会显示内容
// 创建actions实例
let actions = driver.actions({ async: false });
// 移动到元素
await actions.move({ origin: timeEle, x: 1, y: 1}).pause(1000).perform();
注意: 移动到元素获取到tooltip元素内容之后需要将鼠标移出元素,不然有可能会导致下次鼠标移动到其他元素时
        出现两个tooltip元素,导致获取数据错误
  • 日期时间选择框操作
// 主要分为两种情况
// 第一种情况:
//   日期年月日input框是分开的  分别获取年月日的input输入框,填入相对应的数据即可
var monthSelectEle = await driver.wait(until.elementLocated(By.css('select[title="月:"]')), 10000);
await driver.sleep(1000);
await monthSelectEle.click();
await monthSelectEle.sendKeys(month);
await driver.sleep(1000);       
// 获取日期选择框
var daySelectEle = await driver.wait(until.elementLocated(By.css('select[title="日:"]')), 10000);
await daySelectEle.click();
await driver.sleep(1000);
await daySelectEle.sendKeys(day);
await driver.sleep(1000);
// 获取年份选择框
var yearSelectEle = await driver.wait(until.elementLocated(By.css('select[title="年:"]')), 10000);
await driver.sleep(1000);
await yearSelectEle.sendKeys(year);
await driver.sleep(1000);

// 第二种情况:
// 日期年月日input框是一个大的组件,无法分别获取input输入框,获取元素后需要使用sendKeys(Key.TAB) 移动到下一个虚拟输入位
var timeEle = await driver.wait(until.elementLocated(By.id('birthdate')), 10000);
await driver.sleep(5000);
await timeEle.sendKeys(year);
await driver.sleep(1000)
await timeEle.sendKeys(Key.TAB);  // 移动到下一个虚拟输入位
await driver.sleep(1000);
await timeEle.sendKeys(month);
await driver.sleep(1000);
await driver.sleep(1000);
await timeEle.sendKeys(day);
await driver.sleep(1000);
  • 备注-下载图片数据流,通过formData,上传到服务器
// 引用form-data 第三方插件
const FormData = require('form-data');

exports.downLoadMedia = async (mediaUrl, proxyObj) => {
    return new Promise((resolve, reject) => {
        axios({
            method: 'GET',
            url: mediaUrl,
            responseType: 'stream',
            httpsAgent: new https.Agent({ rejectUnauthorized: false }),
            proxy: {
                host:'127.0.0.1',
                port: 7890,
                protocol: "http",
            }
        }).then(res => {
            resolve(res.data)
        }).catch(e => {
            reject(e)
        })
    })
}

/**
 * 下载资源并上传
 * 
 * @param {*} imgUrl
 * @param {*} targetData
 * @param {*} data
 */
async function downloadSource(imgUrl, targetData, data) {
    let str = "";
    try {
        await utils.downLoadMedia(imgUrl).then(async res => {
            let imgType = "";
            let suffix = "";
            if(imgUrl.indexOf('jpg') > -1 || imgUrl.indexOf('jpeg') > -1) {
                imgType = "image/jpeg";
                suffix = 'jpg';
            }else if(imgUrl.indexOf('png') > -1) {
                imgType = "image/png";
                suffix = 'png';
            }else if(imgUrl.indexOf('gif') > -1) {
                imgType = "image/gif"
                suffix = 'gif';
            }else if(imgUrl.indexOf('bmp') > -1) {
                imgType = "image/bmp";
                suffix = 'bmp'
            }else{
                imgType = "image/jpeg";
                suffix= 'jpg';
            }
            const form = new FormData();
            let timeStr = new Date().getTime();
            // // 将 Buffer 转换为 ArrayBuffer
            // const arrayBuffer = imageBytes.buffer;
            // 从 ArrayBuffer 创建 Blob
            // const blob = new Blob([arrayBuffer], { type: imgType });
            form.append('file', res, {
                filename: `${timeStr}.${suffix}`,
                contentType: 'application/octet-stream',
            });
            await utils.uploadRes(form, targetData, data.serverUrlPrefix).then(resUrl => {
                str = resUrl
            })
        })
    } catch (e) {
        console.log('e',e);
        str = ""
    }
    return str;
}
  • 下载图片到本地
// 引用form-data 第三方插件
const FormData = require('form-data');

exports.downLoadMedia = async (mediaUrl, proxyObj) => {
    return new Promise((resolve, reject) => {
        axios({
            method: 'GET',
            url: mediaUrl,
            responseType: 'stream',
            httpsAgent: new https.Agent({ rejectUnauthorized: false }),
            proxy: {
                host:'127.0.0.1',
                port: 7890,
                protocol: "http",
            }
        }).then(res => {
            resolve(res.data)
        }).catch(e => {
            reject(e)
        })
    })
}

/**
 * 下载资源并上传
 * 
 * @param {*} imgUrl
 * @param {*} targetData
 * @param {*} data
 */
async function downloadSource(imgUrl, targetData, data) {
    let str = "";
    try {
        await utils.downLoadMedia(imgUrl).then(async res => {
            let imgType = "";
            let suffix = "";
            if(imgUrl.indexOf('jpg') > -1 || imgUrl.indexOf('jpeg') > -1) {
                imgType = "image/jpeg";
                suffix = 'jpg';
            }else if(imgUrl.indexOf('png') > -1) {
                imgType = "image/png";
                suffix = 'png';
            }else if(imgUrl.indexOf('gif') > -1) {
                imgType = "image/gif"
                suffix = 'gif';
            }else if(imgUrl.indexOf('bmp') > -1) {
                imgType = "image/bmp";
                suffix = 'bmp'
            }else{
                imgType = "image/jpeg";
                suffix= 'jpg';
            }
            const form = new FormData();
            let timeStr = new Date().getTime();
            // // 将 Buffer 转换为 ArrayBuffer
            // const arrayBuffer = imageBytes.buffer;
            // 从 ArrayBuffer 创建 Blob
            // const blob = new Blob([arrayBuffer], { type: imgType });
            form.append('file', res, {
                filename: `${timeStr}.${suffix}`,
                contentType: 'application/octet-stream',
            });
            await utils.uploadRes(form, targetData, data.serverUrlPrefix).then(resUrl => {
                str = resUrl
            })
        })
    } catch (e) {
        console.log('e',e);
        str = ""
    }
    return str;
}

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;