Selenium 簡介
百度百科介紹:
Selenium [1] 是一個用於Web應用程序測試的工具。Selenium測試直接運行在瀏覽器中,就像真正的用戶在操作一樣。支持的瀏覽器包括IE(7, 8, 9, 10, 11),[Mozilla Firefox](https://baike.baidu.com/item/Mozilla Firefox/3504923),Safari,Google Chrome,Opera等。這個工具的主要功能包括:測試與瀏覽器的兼容性——測試你的應用程序看是否能夠很好得工作在不同瀏覽器和操作系統之上。測試系統功能——創建回歸測試檢驗軟件功能和用戶需求。支持自動錄制動作和自動生成 .Net、Java、Perl等不同語言的測試腳本。
使用流程
- 根據平台下載需要的webdrive
Browser | Component |
---|---|
Chrome | chromedriver(.exe) |
Internet Explorer | IEDriverServer.exe |
Edge | MicrosoftWebDriver.msi |
Firefox | geckodriver(.exe) |
Safari | safaridriver |
根據自己的環境進行下載,將下載好的壓縮包解壓到項目的根目錄不需要安裝,各個瀏覽器的版本和dirver的版本的選擇需要相近,不能盲目選擇最新的版本,否則會出現意想不到的bug,建議最好將瀏覽器升級至最新的穩定版本並選擇對應的包。
- 下載依賴包
npm install selenium-webdriver
- 玩一下官方demo, 做適當的代碼修改並根據代碼進行注釋
// 1. 引入selenium-webdriver包,解構需要的對象和方法
const {Builder, By, Key, until} = require('selenium-webdriver');
// 2. 將需要的代碼包在一個自執行函數中
(async function example() {
// 實例化 driver 對象, chrome 代表使用的瀏覽器
let driver = await new Builder().forBrowser('chrome').build();
try {
// 需要打開的網站地址
await driver.get('https://www.baidu.com/');
// Key.RETURN enter回車
// By.id('id') 百度查詢滴輸入內容
// 找到元素 向里面發送一個關鍵字並按回撤
await driver.findElement(By.id('kw')).sendKeys('酒店', Key.RETURN);
// 等1秒之后,驗證是否搜索成功
// await driver.wait(until.titleIs('酒店_百度搜索'), 1000);
} finally {
// 關閉瀏覽器
// await driver.quit();
}
})();
爬取掘金小冊數據
- 實現的功能
- 自動打開掘金頁面的首頁
- 自動點擊小冊、前端進行路由切換
- 將前端的全部小冊數據爬取
- 注意點
- 由於selenium內部都是基於promise進行的封裝,所有的方法調用其實返回的都是一個promise對象,因此會大量的使用async語法
自動打開掘金頁面的首頁
一句話搞定自動打開掘金首頁
// 自動打開掘金
await driver.get('https://juejin.im/timeline');
自動點擊進行路由跳轉
-
在瀏覽器中,查看頁面的布局結構,找到小冊的位置,若是使用jq進行dom選擇,則是 $('.main-header-box .nav-item:nth-of-type(4)')
-
selenium中的By擁有很多的選擇,其使用規則和jq非常相似,By.css('.main-header-box .nav-item:nth-of-type(4)')便可以找到對應的元素,調用click事件就可以模擬自動點擊
// 點擊小冊子的navBar 切換路由到小冊
await driver
.findElement(By.css('.main-header-box .nav-item:nth-of-type(4)'))
.click();
await driver.sleep(1000)
- 當點擊小冊之后,會調用 await driver.sleep(1000),因為當頁面點擊之后,頁面會進行重新渲染,此時防止接下來的操作,獲取不到數據或者獲取的數據不是期望值,增加一個延遲確保數據的准確性
將前端的全部小冊數據爬取
- 根據driver.findElements(By.css('.list-wrap .books-list .item'))獲取前端小冊列表,需要注意的是,當頁面點擊navBar之后,頁面會進行重新渲染,但是此時若是直接去獲取小測列表將會存在風險,因為在頁面沒有渲染完成之前獲取不到期望值,而代碼也會異常終止程序的運行
- 進行迭代取出希望獲取的數據,根據itemInfo.findElement(By.css('.info .title')).getText()
while (true) {
let listViewError = true;
try {
// 獲取小冊列表
let _li = await driver.findElements(By.css('.list-wrap .books-list .item'));
console.log(_li.length);
for (let i = 0, _len = _li.length; i < _len; i++) {
const itemInfo = _li[i];
const title = await itemInfo.findElement(By.css('.info .title')).getText()
const desc = await itemInfo.findElement(By.css('.info .desc')).getText()
let price = await itemInfo.findElement(By.css('.info .price-text')).getText()
_result.push({
title,
desc,
price
})
}
console.log('_result',_result);
} catch (error) {
if (error) listViewError = false;
} finally {
if (listViewError) break;
}
}
在獲取列表的時候,為什么會在最外層增加一個while呢?在 try catch 中的處理又是起到什么作用?
- while能確保會不斷的獲取數據,直到頁面渲染完成獲取到期望的數據
- try catch 可以保證程序在遇到異常時不會直接終止程序,可以繼續運行
- listViewError表示程序是否存在異常情況,若是存在則會進入 catch 中,這個時候 listViewError 為 false,finally 則不會走break,會繼續執行while程序,直到能獲取到數據finally才為true,這個時候 finally中則會break 整個while的循環,跳出循環繼續執行
總結
幾十行的代碼便可以將掘金的小冊數據全部爬到,還是簡單和好用的
全部代碼
/*
* @Author: nordon-wang
* @Date: 2019-08-13 11:05:36
* @Description: 爬取掘金數據
*/
const { Builder, By, Key, until } = require('selenium-webdriver');
let _result = []; // 用來收集獲取的數據
(async function start() {
let driver = await new Builder().forBrowser('chrome').build();
try {
// 自動打開掘金
await driver.get('https://juejin.im/timeline');
// 點擊小冊子的navBar 切換路由到小冊
await driver
.findElement(By.css('.main-header-box .nav-item:nth-of-type(4)'))
.click();
await driver.sleep(1000)
// 點擊二級菜單
await clickViewNav(driver);
await driver.sleep(1000)
// 獲取數據
await getList(driver);
} catch (error) {
console.log(error);
} finally {
let timer = setTimeout(async () => {
clearTimeout(timer);
await driver.quit();
}, 600000);
}
})();
// 獲取渲染完成的按鈕
async function clickViewNav(driver) {
while (true) {
let viewNavError = true;
try {
await driver
.findElement(By.css('.main-container .view-nav .nav-item:nth-of-type(2)'))
.click();
} catch (error) {
if (error) viewNavError = false;
} finally {
if (viewNavError) break;
}
}
}
// 獲取列表數據
// 頁面在渲染完成之前無法獲取到頁面的元素
async function getList(driver) {
while (true) {
let listViewError = true;
try {
// 獲取小冊列表
let _li = await driver.findElements(By.css('.list-wrap .books-list .item'));
console.log(_li.length);
for (let i = 0, _len = _li.length; i < _len; i++) {
const itemInfo = _li[i];
const title = await itemInfo.findElement(By.css('.info .title')).getText()
const desc = await itemInfo.findElement(By.css('.info .desc')).getText()
let price = await itemInfo.findElement(By.css('.info .price-text')).getText()
_result.push({
title,
desc,
price
})
}
console.log('_result',_result);
} catch (error) {
if (error) listViewError = false;
} finally {
if (listViewError) break;
}
}
}