Puppeteer 實戰-爬取動態生成的網頁

Alone1469546971808發表於2018-11-10

一、Puppeteer

Puppeteer 相關介紹與安裝不過多介紹,可通過以下連結進行學習

二、爬取動態網頁

1. 需求

首先,瞭解下我們的需求: 爬取 zoomcharts 文件中 Net Chart 目錄下所有訪問連線對應的頁面,並儲存到本地

Puppeteer 實戰-爬取動態生成的網頁

2. 研究 ZoomCharts 文件頁面結構

首先,我們得研究透 ZoomCharts 頁面如何載入,以及左側導航的 DOM 樹結構,才好進行下一步操作

  • 頁面首次載入

    Puppeteer 實戰-爬取動態生成的網頁
    頁面首次載入,左側導航第一個目錄Introduction 高亮,從控制檯可看出,該元素增加了 active 類,同時 li[data-section="net-chart"] 節點下只有一個元素節點 a

  • 點選 Net Chart 目錄

Puppeteer 實戰-爬取動態生成的網頁

點選 Net Chart 目錄, Net Chart目錄高亮,下拉顯示子目錄,檢視控制檯,其元素節點增加 active 類,並增加 ul 子元素節點, 此時,第一個子目錄節點也只有一個子元素節點 a

  • 結論

不難發現, 左側目錄是動態生成的,而不是靜態寫死的,只有點選父級目錄,其子目錄才會生成顯示,同時,父級目錄元素上的 drop 類表明存在子級目錄

3. 編寫主程式

通過上面分析,得出大概流程如下

  1. 從上到下,遍歷 Net Chart 目錄的 DOM 樹,當找到 a.drop 的元素節點,模擬滑鼠點選事件 click,生成子目錄節點
  2. 找到 Net Chart 目錄下所有的 a 連結,生成一個陣列
  3. 遍歷陣列,訪問每一個子目錄頁面,儲存頁面的 html 檔案到本地

接下來實現每個具體流程

  • 專案初始化

安裝 puppeteer, rimraf(資料夾操作時需用到)

npm i -S puppeteer rimraf
複製程式碼

新建 test.js 檔案並引入

const puppeteer = require('puppeteer');
const chalk = require('chalk');
const path = require('path');
const https = require('https');
const fs = require('fs');
const rm = require('rimraf');

const settings = {
    headless: false
}

function resolve(dir, dir2 = '') {
	return path.posix.join(__dirname, './', dir, dir2);
}

async function main () {
    const browser = await puppeteer.launch(settings); // 建立一個Browser 物件
    try {
        const page = await browser.newPage(); // 使用 Browser 建立 Page 
        page.setDefaultNavigationTimeout(600000);
        // 監聽 console 
        page.on('console', msg => {
            for (let i = 0; i < msg.args().length; ++i) {
                console.log(`${i}: ${msg.args()[i]}`);
            }
        });
        
        <!-- main start -->
        // main 區域
        
        <!-- end start-->
        console.log('服務正常結束')
    } catch (error) {
        console.log('服務出現錯誤:')
        console.log(error)
    } finally {
        
    }
}

main()
複製程式碼

接下來所有程式碼都在 main 區域內完成, 完整程式碼可訪問 github程式碼倉庫 檢視,下面僅列出每部分的思路

  • 建立資料夾,用於儲存爬取的檔案

    • 定義檔案輸出路徑
    • 根據路徑生成資料夾
    • 當資料夾已經存在,先刪除,再新建
  • 實現 Net Chart 目錄下所有 a.drop 元素的點選事件

這部分涉及到DOM 操作, 只有在 page.evaluate() 中才能訪問真實的DOM元素,同時,在page.evaluate() 中不能直接呼叫外面定義的函式,可將函式傳遞進去,或將函式繫結到 window 物件上

await page.evaluate(async () => {
    const rootNode = document.querySelector('#menu > ul > li:nth-child(5) > ul > li:nth-child(5)');
    await window.walkDOM(rootNode)
})
複製程式碼

此時,繫結到window物件上的 walkDOM 函式需要在page.evaluateOnNewDocument函式中定義才能生效

await page.evaluateOnNewDocument(() => {
    // 遍歷DOM
    window.walkDOM = (node) => {
        if (node === null) {
            return
        }
        if (node.tagName === 'A' && node.className.indexOf('drop') > -1) {
            node.click() // 點選事件
        }
        node = node.firstElementChild
        while (node) {
            walkDOM(node)
            node = node.nextElementSibling
        }
    }
})
複製程式碼

當Net Chart 目錄下所有 a.drop 元素點選過後,Net Chart 目錄下所有後代子目錄都會載入生成,接下來操作就簡單了

  • 獲取Net Chart 目錄下所有 a 元素

    • 通過 document.querySelectorAll()查詢到所有a元素,儲存到陣列
    • 遍歷陣列,對陣列每一項進行處理成 {href: '',text: ''} 物件
    • 返回物件陣列
  • 遍歷物件陣列, 訪問每一個連結,下載其HTML檔案

    • 跳轉每一個連結,下載需要的html到指定資料夾
    • 當 HTML 中存在 img 時,下載所有圖片

4. 總結

第一次使用Puppeteer也是磕磕絆絆,花費不少時間,期間也參考了不少文章,還需多多練習

程式碼倉庫

參考文章

相關文章