如今網頁大可能是動態網頁,若是隻是單純地經過爬取網頁HTML文件,根本爬取不到須要後續加載的商品價格或圖片等重要信息,更別談那些喪心病狂的登陸限制,對於小爬蟲來講,去分析那些複雜的腳本得不償失,更別談網站還會與時俱進地更新,好不容易破解了,人家一更新又得從頭來,這都大大提升了小爬蟲的難度。javascript
但幸虧,在Node.js裏有那麼一款神器,無懼網站的登陸限制和反爬蟲措施,以不變應萬變,經過一招簡單的模擬用戶操做就能破解絕大部分限制,它就是由谷歌出品的爬取動態網頁神器Puppeteer。java
Puppeteer本質上是一個chrome瀏覽器,只不過能夠經過代碼進行各類操控。好比模擬鼠標點擊、鍵盤輸入等操做,有點像按鍵精靈,網頁很難分清這是人類用戶仍是爬蟲,因此限制也就無處談起。git
它的好處在於簡單,很是簡單,多是在全部能夠爬取動態網頁的庫裏最簡單的一個。github
但壞處也很明顯,那就是速度慢,效率有點低。它等於每次運行都會啓動一個Chrome瀏覽器,因此運行效率上遠遠比不過其它庫,並不適合爬取大數據。但對於小爬蟲來講已經綽綽有餘了。chrome
接下來以我寫過的爬取jd商品頁面的小爬蟲爲例,來看看這款有多簡單。 當初寫這個爬蟲是爲了買蘋果的妙控板,找了一圈後發現jd奪寶島裏的價格很誘人,這也應該是奪寶島裏惟一值得搶的商品,可是數量稀少,好久纔會出現一個。npm
因而就想到了監控商品頁面,一旦發現新的妙控板就彈出提醒。甚至還能夠實現自動競拍,但我沒寫,畢竟除了觸控板之外我都不想買,沒辦法測試可否成功拍到。瀏覽器
OK,開始吧!less
先安裝Puppeteer庫,用到的也就只有這個庫:async
npm install puppeteer
複製代碼
連接網頁也很是簡單,只須要幾行代碼:函數
//啓動瀏覽器
const browers = await puppeteer.launch()
//啓動新頁面
const page = await browers.newPage()
//連接網址
await page.goto(url)
複製代碼
這樣子就連接成功了!Puppeteer.launch()還能夠接收不少參數,但這裏咱們用到的只有headless,默認爲ture,若是是false的話會顯示瀏覽器界面。咱們能夠利用這個特性實現彈出窗口提醒,一旦發現有符合條件的商品就將headless改爲false。
在連接網頁後接下來就是爬取商品信息,而後進行分析。
網址: 妙控板
經過頁面能夠看到,一旦有同類商品會出如今旁邊的同類奪寶裏,咱們只須要爬取那裏的信息就好了,有兩種方式:
一種是$eval,至關於js裏的document.querySelector,只爬取符合的第一個元素;
另外一種爲$$eval,至關於js裏的document.querySelectorAll,爬取全部符合的元素;
它們接收的第一個參數是元素地址,第二個參數是回調函數,操做和document.querySelector同樣,來看代碼:
//咱們拿到同類奪寶裏的全部子元素
const goods = page.$$eval('#auctionRecommend > div.mc > ul > li', ele => ele)
複製代碼
如今已經拿到了同類奪寶裏全部商品的標籤信息,接下來開始分析信息。 獲取裏面全部商品的名稱,而後對照關鍵字是否存在,若是存在則將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)
}
})
複製代碼
對於這個小爬蟲來講,損失的效率並很少,沒什麼優化的必要,但做爲一個強迫症,仍是但願能去掉的儘可能去掉。
在這個爬蟲裏,咱們根本不用看任何圖片信息,因此全部圖片都沒有加載的必要,爲了提高一點點運行效率,將全部圖片攔截掉:
//開啓攔截器
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();
}
})
複製代碼
在瀏覽器彈出時,會發現打開的窗口顯示範圍很小,不只不方便瀏覽,可能還會致使點擊或輸入等操做出錯,因此仍是有必要進行調整:
await page.setViewport({
width: 1920,
height: 1080,
})
複製代碼
至此,全部代碼已經完成了,快試試效果吧!
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… 若是對你有幫助,歡迎關注我,我會持續輸出更多好文章!