0. 前言
這兩天對爬蟲開始感興趣,最開始是源於天涯的一個房價神貼,蓋了上萬層,追着讀了好久。天涯網頁端的“只看樓主”需要會員,手機端可以“只看樓主”,但是體驗不太好,記錄也不方便,於是決定把樓主發言單獨爬下來,既可以保存,也可以檢索。
最開始想法很簡單,對每一頁進行元素檢索,發帖人與樓主名字匹配的,就把里面的content拷出來。
首先在網上找到的工具是cheerio
插件,它在讀取網站之后,將網站內容存下來,通過元素選擇器進行內容選取。在使用遞歸后,還能解決翻頁問題。
事實上也確實如此,通過簡單幾步操作,就把樓主的發言保存了下來,也讓我對爬蟲產生了興趣。
問題
cheerio
確實簡單好用,在應對簡單靜態網頁時沒有問題。但對付具備一定反爬機制的網站就無能為力了。比如cheerio
解決翻頁問題,靠的是動態修改url
鏈接。但是有的網站,比如我最愛的煎蛋,它的網頁鏈接頁碼是亂碼,就沒辦法實現自動翻頁。再比如有的房產網站,在羅列在售資源時,為了用戶體驗,使用了懶加載,只有將頁面滾動到底部后,才能觸發加載。
以上種種實際上就是cheerio
對於網頁操作是無能為力的。
解決
在網上查找對付懶加載的方法時,發現了puppeteer
插件。谷歌瀏覽器在17年自行開發了Chrome Headless特性,並與之同時推出了puppeteer,本質上就是一個不含界面的瀏覽器,有點像電腦的終端,所有操作都通過代碼進行操作。
這樣,我們就可以在對網站進行檢索之前,操作指定元素滾動到底部,以觸發更多信息。或者在需要翻頁的時候,操作代碼對翻頁按鈕進行點擊,然后對翻頁后的頁面進行相關處理。
1. 下載與引包
// 下載
npm i puppeteer
// 引包
const puppeteer = require('puppeteer')
2. 使用步驟
// 將整個操作放置在一個閉包的異步函數中,以便於進行異步操作
(async () => {
// 1. 使用puppetee插件啟動一個瀏覽器,並開啟一個新頁面
const brower = await puppeteer.launch({
args: ['--no-sandbox'],
dumpio: false,
headless:false, // 默認為true,設為false時,可以顯示可視化瀏覽器界面
})
const page = await brower.newPage() // 開啟一個新頁面
// 2. 打開指定網頁
await page.goto('http://jandan.net/ooxx', {
waitUntil: 'networkidle2' // 網絡空閑說明已加載完畢
});
// 3. 對動態網站進行自動化操作,這一步是其精髓所在
// 由於我們監控的是動態網頁,剛打開網頁時,所需元素也許還未出現,所以需要進行監聽,例如“下一頁按鈕”
await page.waitForSelector('a.previous-comment-page'); // 括號內是元素選擇器
// 當下一頁按鈕出現時,模擬點擊
await page.click('a.previous-comment-page')
// 4. 這時我們可以執行爬取我們需要的數據了,我們可以去審查頁面的dom結果,來循環遍歷這些數據。
// page.evaluate() 為在瀏覽器中執行函數,相當於在控制台中執行函數,返回一個 Promise
const result = await page.evaluate(() => {
// 拿到頁面上的jQuery
var $ = window.$;
// 在這里進行熟悉的 DOM 操作
// Do something
});
// 5. 關閉瀏覽器,在console里面打印我們需要的數據
brower.close();
// 6. 對結果進行處理
console.log(result);
})();
3. 爬過的幾個坑
page.evaluate 的傳參問題
因為打開的這個 page 只是一個木偶,並不是真正的瀏覽器頁面,所以在這個頁面上的操作與一般頁面上的操作有差異。
官方文檔里說,這個參數是這樣的。在實際使用中,可以傳一個字符串變量,但是到更復雜一點的,比如‘fs’,自定義外部函數時,都無法讀取。
這也是我建議在第6步,對頁面操作完成后,統一對結果進行處理。(主要是因為我沒有解決這個問題,所以認慫繞開走了……)
元素操作問題
puppeteer
中,最重要的函數執行和要素選擇都與一般瀏覽器上操作有些區別,這里有些坑要爬,現在我也說不清楚。