­

Puppeteer萬物皆可爬

  • 2019 年 12 月 22 日
  • 筆記

puppeteer 是一個Chrome官方出品的headless Chrome node庫。它提供了一系列的API, 可以在無UI的情況下調用Chrome的功能, 適用於爬蟲、自動化處理等各種場景

puppteer

puppeteer 是一個Chrome官方出品的headless Chrome node庫(沒有圖形用戶介面的的web瀏覽器)。它提供了一系列的API, 可以在無UI的情況下調用Chrome的功能, 適用於爬蟲、自動化處理等各種場景

可以用它來幹什麼?

  • 生成頁面截圖和PDF
  • 自動化表單提交、UI 測試、鍵盤輸入等
  • 創建一個最新的自動化測試環境。使用最新的 JavaScript 和瀏覽器功能,可以直接在最新版本的 Chrome 中運行測試。
  • 爬取 SPA 頁面並進行預渲染(即'SSR')

和cheerio的區別

  • cheerio – 這貨說白了就是類似jq語法操作的html文檔庫,只能爬取靜態的html,無法獲取ajax數據,一般都axios+cherrio結合使用
  • puppteer – 能夠模擬瀏覽器運行環境,能夠請求網站資訊。能夠進行模擬操作(點擊/滑動/hover等),甚至能注入node腳本到瀏覽器內部環境運行

puppteer架構圖

  • Puppeteer – 通過 devTools 與 browser 通訊
  • Browser – 一個可以擁有多個頁面的瀏覽器(chroium)實例
  • Page – 至少含有一個 Frame 的頁面
  • Frame – 至少還有一個用於執行 javascript 的執行環境,也可以拓展多個執行環境

輕鬆入門

const puppeteer = require('puppeteer');(async () => {  const browser = await puppeteer.launch();  const page = await browser.newPage();  await page.goto(targetUrl);  await page.screenshot({path: 'example.png'});  await browser.close();})();

分析程式碼

1.引入puppeteer

const puppeteer = require('puppeteer');

2.生成實例

也就是通過Puppeteer啟動一個瀏覽器環境

const browser = await puppeteer.launch(options);

options:

  • executablePath: puppeteer.executablePath() – 獲取默認可執行的chrome位置
  • headless: false – 是否開啟headless模式
  • slowMo: 250 – 該選項會是puppeteer操作減慢指定的毫秒數
  • devtools: true – 在應用程式程式碼瀏覽器中使用調試器
  • defaultViewport(object) – 默認800 x 600
    • width
    • height
    • deviceScaleFactor – 比例因子
    • isMobile – 是否考慮meta viewport 標籤, 默認為false
    • hasTouch – 指定viewport是否支援觸摸事件,默認為false
    • isLandscape – 指定是否處於橫向模式
  • 更多參數請參照Puppeteer.launch()[1]

3.打開一個新頁面

const page = await browser.newPage();

4.前往目標頁面

await page.goto(targetUrl);

注意:這裡可接受第二個參數,是個對象,用來進行一些簡單的配置,待選項有

waitUntil:

  • load – 請求到數據後立即返回
  • domcontentloaded – dom載入完成後返回
  • networkidle0 – 沒有超過0個網路連接500ms後返回
  • networkidle2 – 沒有超過2個網路連接500ms後返回

timeout: 跳轉等待時間,單位是毫秒, 默認是30秒, 傳 0 表示無限等待,可以通過page.setDefaultNavigationTimeout(timeout)方法修改默認值

referer(不常用): 引用頁頭的值。如果提供,它將優先於page.setExtraHTTPHeaders()設置的referer頭值(Referer header value. If provided it will take preference over the referer header value set by page.setExtraHTTPHeaders().)

5.關閉瀏覽器

 browser.close();

花里胡哨

其實輕鬆入門節已經將我們常用的功能進行了相對完善的描述,總結一下,爬一個網頁需要幾步

  1. 打開瀏覽器
  2. 關閉瀏覽器

是不是很簡單?問題來了?怎麼爬?會不會用jq?會用jq你就會用爬蟲。

找到一個自己喜歡的影片網站,(以下內容僅供教學!)

const demo = async () => {  const browser = await (puppeteer.launch({    executablePath: puppeteer.executablePath(),    headless: false  }))  var arr = []  for (let i = 1; i <= 40; i++) {    console.log('正在抓取全職高手第' + i + '集')    const targetUrl = `https://goudaitv1.com/play/78727-4-${i}.html`    console.log(targetUrl)    const page = await browser.newPage()    await page.goto(targetUrl, {      timeout: 0,      waitUntil: 'domcontentloaded'    })    const baseNode = '.row'    const movieList = await page.evaluate((sel) => {      var stream = Array.from($(sel).find('iframe#Player').attr('src'))      stream && (stream = stream.join(''))      return stream    }, baseNode)    arr.push(movieList)    page.close()  }  console.log(arr)  browser.close()}

page.evaluate(pageFunction[, …args])

  • pageFunction <function|string> 要在頁面實例上下文中執行的方法
  • …args 要傳給 pageFunction 的參數
  • 返回: pageFunction執行的結果

如果pageFunction返回的是Promise,page.evaluate將等待promise完成,並返回其返回值。

如果pageFunction返回的是不能序列化的值,將返回undefined

給pageFunction傳參數示例:

const result = await page.evaluate(x => {  return Promise.resolve(8 * x);}, 7); // (註:7 可以是你自己程式碼里任意方式得到的值)console.log(result); // 輸出 "56"

也可以傳入一個字元串

console.log(await page.evaluate('1 + 2')); // 輸出 "3"const x = 10;console.log(await page.evaluate(`1 + ${x}`)); // 輸出 "11"

存入資料庫

搞定!你可以用這些數據做自己想做的一切,比如

最後

當然,'爬'只是它的冰山一角,上述demo比較偷懶的直接獲取了標籤的地址進行跳轉,我們還可以使用點擊事件進行頁面跳轉,感興趣的可以試試。

page.click(selector[, options])

  • selector 要點擊的元素的選擇器。如果有多個匹配的元素, 點擊第一個。
  • options
    • button left, right, 或者 middle, 默認是 left。
    • clickCount 默認是 1. 查看 UIEvent.detail。
    • delay mousedown 和 mouseup 之間停留的時間,單位是毫秒。默認是0
  • 返回: Promise對象,匹配的元素被點擊。如果沒有元素被點擊,Promise對象將被rejected。

此方法找到一個匹配 selector 選擇器的元素,如果需要會把此元素滾動到可視,然後通過 page.mouse 點擊它。如果選擇器沒有匹配任何元素,此方法將會報錯。

要注意如果 click() 觸發了一個跳轉,會有一個獨立的 page.waitForNavigation() Promise對象需要等待。正確的等待點擊後的跳轉是這樣的:

const [response] = await Promise.all([  page.waitForNavigation(waitOptions),  page.click(selector, clickOptions),]);
  • page.waitForNavigation([options])

此方法在頁面跳轉到一個新地址或重新載入時解析,如果你的程式碼會間接引起頁面跳轉,這個方法比較有用。

更多參照page.waitForNavigation([options])[2]

參考

Puppeteer 中文網[3]

Puppeteer npm[4]

參考資料

[1]

Puppeteer.launch(): https://github.com/GoogleChrome/puppeteer/blob/v1.20.0/docs/api.md#puppeteerlaunchoptions

[2]

page.waitForNavigation([options]): https://zhaoqize.github.io/puppeteer-api-zh_CN/#?product=Puppeteer&version=v1.20.0&show=api-pagewaitfornavigationoptions

[3]

Puppeteer 中文網: https://zhaoqize.github.io/puppeteer-api-zh_CN/

[4]

Puppeteer npm: https://www.npmjs.com/package/puppeteer