用Node.js爬取動態網頁,這多是最簡潔的方式

如今網頁大可能是動態網頁,若是隻是單純地經過爬取網頁HTML文件,根本爬取不到須要後續加載的商品價格或圖片等重要信息,更別談那些喪心病狂的登陸限制,對於小爬蟲來講,去分析那些複雜的腳本得不償失,更別談網站還會與時俱進地更新,好不容易破解了,人家一更新又得從頭來,這都大大提升了小爬蟲的難度。javascript

但幸虧,在Node.js裏有那麼一款神器,無懼網站的登陸限制和反爬蟲措施,以不變應萬變,經過一招簡單的模擬用戶操做就能破解絕大部分限制,它就是由谷歌出品的爬取動態網頁神器Puppeteer。java

1.Puppeteer的好處和壞處

Puppeteer本質上是一個chrome瀏覽器,只不過能夠經過代碼進行各類操控。好比模擬鼠標點擊、鍵盤輸入等操做,有點像按鍵精靈,網頁很難分清這是人類用戶仍是爬蟲,因此限制也就無處談起。git

它的好處在於簡單,很是簡單,多是在全部能夠爬取動態網頁的庫裏最簡單的一個。github

但壞處也很明顯,那就是速度慢,效率有點低。它等於每次運行都會啓動一個Chrome瀏覽器,因此運行效率上遠遠比不過其它庫,並不適合爬取大數據。但對於小爬蟲來講已經綽綽有餘了。chrome

接下來以我寫過的爬取jd商品頁面的小爬蟲爲例,來看看這款有多簡單。 當初寫這個爬蟲是爲了買蘋果的妙控板,找了一圈後發現jd奪寶島裏的價格很誘人,這也應該是奪寶島裏惟一值得搶的商品,可是數量稀少,好久纔會出現一個。npm

因而就想到了監控商品頁面,一旦發現新的妙控板就彈出提醒。甚至還能夠實現自動競拍,但我沒寫,畢竟除了觸控板之外我都不想買,沒辦法測試可否成功拍到。瀏覽器

OK,開始吧!less

2.首先第一步要安裝Puppeteer:

先安裝Puppeteer庫,用到的也就只有這個庫:async

npm install puppeteer
複製代碼

3.第二步連接網頁

連接網頁也很是簡單,只須要幾行代碼:函數

//啓動瀏覽器
const browers = await puppeteer.launch()
//啓動新頁面
const page = await browers.newPage()
//連接網址
await page.goto(url)
複製代碼

這樣子就連接成功了!Puppeteer.launch()還能夠接收不少參數,但這裏咱們用到的只有headless,默認爲ture,若是是false的話會顯示瀏覽器界面。咱們能夠利用這個特性實現彈出窗口提醒,一旦發現有符合條件的商品就將headless改爲false。

4.爬取商品信息

在連接網頁後接下來就是爬取商品信息,而後進行分析。

網址: 妙控板

4.1獲取相對應的元素標籤

經過頁面能夠看到,一旦有同類商品會出如今旁邊的同類奪寶裏,咱們只須要爬取那裏的信息就好了,有兩種方式:

一種是$eval,至關於js裏的document.querySelector,只爬取符合的第一個元素;

另外一種爲$$eval,至關於js裏的document.querySelectorAll,爬取全部符合的元素;

它們接收的第一個參數是元素地址,第二個參數是回調函數,操做和document.querySelector同樣,來看代碼:

//咱們拿到同類奪寶裏的全部子元素
const goods = page.$$eval('#auctionRecommend > div.mc > ul > li', ele => ele)
複製代碼

4.2.分析商品信息

如今已經拿到了同類奪寶裏全部商品的標籤信息,接下來開始分析信息。 獲取裏面全部商品的名稱,而後對照關鍵字是否存在,若是存在則將headless改成false彈出窗口提醒,若是不存在則在半小時後再次連接。

Puppeteer提供了一個等待命令page.waitFor(),不只能夠按時間等待,也能夠按某個元素的加載進度進行等待。

const goods = page.$$eval('#auctionRecommend > div.mc > ul > li', el => {
	  //錯誤和關鍵字不存在都會返回false,接着循環
    try {
        for (let i = 0; i < el.length; i++) {
            let n = el[i].querySelector('div.p-name').textContent
            if(n.includes('妙控板')){
                return true
            } else {
                return false
            }
        }
    } catch (error) {
        return false
    }
})

if(!bool){
    return console.log('網頁已打開,再也不監控')
}

await goods.then(async (b) => {
    if(b){
        console.log('有貨了!')
        await page.waitFor(2000)
        await browers.close()
        return requestUrl(false)
    }  else {
        console.log('還沒貨')
        console.log('三十分鐘後再嘗試')
        await page.waitFor(1800000)
        await browers.close()
        return requestUrl(true)
    }
})
複製代碼

5.優化代碼

對於這個小爬蟲來講,損失的效率並很少,沒什麼優化的必要,但做爲一個強迫症,仍是但願能去掉的儘可能去掉。

5.1攔截圖片

在這個爬蟲裏,咱們根本不用看任何圖片信息,因此全部圖片都沒有加載的必要,爲了提高一點點運行效率,將全部圖片攔截掉:

//開啓攔截器
await page.setRequestInterception(true)
await page.on('request',interceptedRequest => {
    //判斷url是否以jpg或png結尾,符合條件將再也不加載
    if(interceptedRequest.url().endsWith('.jpg') || interceptedRequest.url().endsWith('.png')){
        interceptedRequest.abort();
    }else{
        interceptedRequest.continue();
    }
})
複製代碼

5.2調整窗口大小

在瀏覽器彈出時,會發現打開的窗口顯示範圍很小,不只不方便瀏覽,可能還會致使點擊或輸入等操做出錯,因此仍是有必要進行調整:

await page.setViewport({
    width: 1920,
    height: 1080,
})
複製代碼

至此,全部代碼已經完成了,快試試效果吧!

6.完整代碼

const puppeteer = require('puppeteer')

const url = 'https://paipai.jd.com/auction-detail/114533257?entryid=p0120003dbdnavi'

const requestUrl = async function(bool){
    const browers = await puppeteer.launch({headless:bool})
    const page = await browers.newPage()

    await page.setRequestInterception(true)
    await page.on('request',interceptedRequest => {
        if(interceptedRequest.url().endsWith('.jpg') || interceptedRequest.url().endsWith('.png')){
            interceptedRequest.abort();
        }else{
            interceptedRequest.continue();
        }
    })

    await page.setViewport({
        width: 1920,
        height: 1080,
    })

    await page.goto(url)

    const goods = page.$$eval('#auctionRecommend > div.mc > ul > li', el=>{
        try {
            for (let i = 0; i < el.length; i++) {
                let n = el[i].querySelector('div.p-name').textContent
                if(n.includes('妙控板')){
                    return true
                } else {
                    return false
                }
            }
        } catch (error) {
            return false
        }
    })

    if(!bool){
        return console.log('網頁已打開,再也不監控')
    }
    
    await goods.then(async (b)=>{
        if(b){
            console.log('有貨了!')
            await page.waitFor(2000)
            await browers.close()
            return requestUrl(false)
        }  else {
            console.log('還沒貨')
            console.log('三十分鐘後再嘗試')
            await page.waitFor(1800000)
            await browers.close()
            return requestUrl(true)
        }
    })
}

requestUrl(true)
複製代碼

也能夠經過Github獲取完整代碼:github.com/Card007/Nod… 若是對你有幫助,歡迎關注我,我會持續輸出更多好文章!

相關文章
相關標籤/搜索