Puppeteer 實戰-爬取動態生成的網頁
Puppeteer 相關介紹與安裝不過多介紹,可通過以下連結進行學習
- Chrome%2Fpuppeteer%2F" rel="nofollow,noindex">開源地址
- 英文文件
- 中文社群
- 掘金Puppeteer專欄
二、爬取動態網頁
1. 需求
首先,瞭解下我們的需求: 爬取zoomcharts 文件中 Net Chart
目錄下所有訪問連線對應的頁面,並儲存到本地

2. 研究 ZoomCharts 文件頁面結構
首先,我們得研究透 ZoomCharts 頁面如何載入,以及左側導航的 DOM 樹結構,才好進行下一步操作
-
頁面首次載入
Introduction
高亮,從控制檯可看出,該元素增加了active
類,同時li[data-section="net-chart"]
節點下只有一個元素節點a
-
點選
Net Chart
目錄

點選 Net Chart
目錄, Net Chart
目錄高亮,下拉顯示子目錄,檢視控制檯,其元素節點增加 active
類,並增加 ul
子元素節點, 此時,第一個子目錄節點也只有一個子元素節點 a
- 結論
不難發現, 左側目錄是動態生成的,而不是靜態寫死的,只有點選父級目錄,其子目錄才會生成顯示,同時,父級目錄元素上的 drop
類表明存在子級目錄
3. 編寫主程式
通過上面分析,得出大概流程如下
- 從上到下,遍歷
Net Chart
目錄的 DOM 樹,當找到a.drop
的元素節點,模擬滑鼠點選事件click
,生成子目錄節點 - 找到
Net Chart
目錄下所有的a
連結,生成一個數組 - 遍歷陣列,訪問每一個子目錄頁面,儲存頁面的 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檔案
img