使用selenium進行爬取掘金前端小冊的資料

wyao發表於2019-08-13

Selenium 簡介

百度百科介紹:

Selenium [1] 是一個用於Web應用程式測試的工具。Selenium測試直接執行在瀏覽器中,就像真正的使用者在操作一樣。支援的瀏覽器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等。這個工具的主要功能包括:測試與瀏覽器的相容性——測試你的應用程式看是否能夠很好得工作在不同瀏覽器和作業系統之上。測試系統功能——建立迴歸測試檢驗軟體功能和使用者需求。支援自動錄製動作和自動生成 .Net、Java、Perl等不同語言的測試指令碼。

使用流程

  1. 根據平臺下載需要的webdrive
Browser Component
Chrome chromedriver(.exe)
Internet Explorer IEDriverServer.exe
Edge MicrosoftWebDriver.msi
Firefox geckodriver(.exe)
Safari safaridriver

根據自己的環境進行下載,將下載好的壓縮包解壓到專案的根目錄不需要安裝,各個瀏覽器的版本和dirver的版本的選擇需要相近,不能盲目選擇最新的版本,否則會出現意想不到的bug,建議最好將瀏覽器升級至最新的穩定版本並選擇對應的包。

  1. 下載依賴包
npm install selenium-webdriver
  1. 玩一下官方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');

自動點選進行路由跳轉

  1. 在瀏覽器中,檢視頁面的佈局結構,找到小冊的位置,若是使用jq進行dom選擇,則是 $('.main-header-box .nav-item:nth-of-type(4)')
    使用selenium進行爬取掘金前端小冊的資料

  2. 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)
  1. 當點選小冊之後,會呼叫 await driver.sleep(1000),因為當頁面點選之後,頁面會進行重新渲染,此時防止接下來的操作,獲取不到資料或者獲取的資料不是期望值,增加一個延遲確保資料的準確性

將前端的全部小冊資料爬取

  1. 根據driver.findElements(By.css('.list-wrap .books-list .item'))獲取前端小冊列表,需要注意的是,當頁面點選navBar之後,頁面會進行重新渲染,但是此時若是直接去獲取小測列表將會存在風險,因為在頁面沒有渲染完成之前獲取不到期望值,而程式碼也會異常終止程式的執行
  2. 進行迭代取出希望獲取的資料,根據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 中的處理又是起到什麼作用?

  1. while能確保會不斷的獲取資料,直到頁面渲染完成獲取到期望的資料
  2. try catch 可以保證程式在遇到異常時不會直接終止程式,可以繼續執行
  3. listViewError表示程式是否存在異常情況,若是存在則會進入 catch 中,這個時候 listViewError 為 false,finally 則不會走break,會繼續執行while程式,直到能獲取到資料finally才為true,這個時候 finally中則會break 整個while的迴圈,跳出迴圈繼續執行

總結

幾十行的程式碼便可以將掘金的小冊資料全部爬到,還是簡單和好用的

全部程式碼

/*
 * @Author: nordon-wang
 * @Date: 2019-08-13 11:05:36
 * @Description: 爬取掘金資料
 * @Email: nordon-wang@oyohotels.cn
 */

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;
    }
  }
}

相關文章