一、Puppeteer
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檔案
- 跳轉每一個連結,下載需要的html到指定資料夾
- 當 HTML 中存在
img
時,下載所有圖片
4. 總結
第一次使用Puppeteer
也是磕磕絆絆,花費不少時間,期間也參考了不少文章,還需多多練習
程式碼倉庫
參考文章