puppeteer介紹
對於靜態頁面的爬取是灰常簡單的,一個request+cherrico即可,今天我動手對英雄聯盟官網英雄資料爬取時發現英雄列表和詳情頁是通過js異步渲染的數據,所以就用上了這個神器puppeteer
Puppeteer能夠模擬一個瀏覽器的運行環境,能夠請求網站信息,並運行網站內部的邏輯。然後再通過WS協議動態的獲取頁面內部的數據,並能夠進行任何模擬的操作(點擊、滑動、hover等),並且支持跳轉頁面,多頁面管理。甚至能注入node上的腳本到瀏覽器內部環境運行,總之,你能對一個網頁做的操作它都能做,你不能做的它也能做。
環境和安裝
Puppeteer 至少需要 Node v6.4.0,如要使用 async / await,只有 Node v7.6.0 或更高版本才支持。 node下載地址: https://nodejs.org/zh-cn/
裝puppeteer: yarn add puppeteer --ignore-scripts 或者 npm i --save puppeteer --ignore-scripts,爲什麼要加--ignore-scripts?因爲puppeteer默認是會下載Chromium,直接下載Chromium會失敗,加了--ignore-scripts 會跳過這一步
所以需要單獨下載Chromium,下載地址:https://download-chromium.appspot.com/ ,沒有翻牆工具的話點這裏下載,還是很貼心噶^_^
把下載剛剛下載的文件解壓備用
代碼
const chalk = require('chalk')
count fs = require("fs")
const devices = require('puppeteer/DeviceDescriptors')//用來更改puppeteer訪問設備
const puppeteer = require('puppeteer');
puppeteer.launch({
headless: false, //不使用無頭模式使用本地可視化
executablePath: "C:/Users/Administrator/Desktop/express-requetsAPI--master/chrome-win/chrome.exe", //因爲是yarn add puppeteer --ignore-scripts沒有安裝chromium,需要制定本地chromium的chrome.exe路徑所在,剛纔下載後解壓後的全路徑
//設置超時時間
timeout: 15000,
//如果是訪問https頁面 此屬性會忽略https錯誤
ignoreHTTPSErrors: true,
// 打開開發者工具, 當此值爲true時, headless總爲false
devtools: true,
}).then(async browser => {
function formatProgress (current, TOTAL_PAGE) {
let percent = (current / TOTAL_PAGE) * 100
let done = ~~(current / TOTAL_PAGE * 40)
let left = 40 - done
let str = `英雄個數:${TOTAL_PAGE} 當前進度:[${''.padStart(done, '=')}${''.padStart(left, '-')}] 已完成${Math.round(percent*100) / 100}%` //padStart es7
return str
}
const page = await browser.newPage()
page.on('console', msg => {
if (typeof msg === 'object')
console.dir(msg)
else
console.log(chalk.blue(msg))
})
await page.setViewport({width: 1920, height: 1080});
// await page.emulate(devices['iPhone X'])
await page.goto('http://lol.qq.com/data/info-heros.shtml', { waitUntil: "networkidle2" })
await page.waitFor(1000)
// console.log(await page.$eval('#jSearchHeroDiv li a', el => el.href))
let heros = await page.$$('#jSearchHeroDiv li a')
let re = ""
for(let i = 0; i < heros.length; i++){
// if(i==5) break;
await page.goto('http://lol.qq.com/data/info-heros.shtml', { waitUntil: "networkidle2" })
await page.waitFor(2000)
heros = await page.$$('#jSearchHeroDiv li a')
await heros[i].click()
await page.waitFor(3000)
await handleData(i, heros.length)
console.clear()
console.log(chalk.yellow(formatProgress(i, heros.length)))
console.log(chalk.yellow('第'+(i+1)+'個英雄數據加載完畢'))
await page.waitFor(2000)
}
async function handleData(i, len) {
let result = await page.evaluate(() => {
let data = {}
data.DATAname = document.querySelector('#DATAname').innerText
data.DATAtitle = document.querySelector('#DATAtitle').innerText
let tags = document.querySelectorAll('#DATAtags span')
data.DATAtags = Array.from(tags).map(v => v.innerText)
let keys = document.querySelectorAll('#DATAinfo dt')
let values = document.querySelectorAll('#DATAinfo dd i')
data.DATAinfo = []
keys.forEach((v, i) => {
data.DATAinfo.push({type: v.innerHTML, value: values[i].style.width})
})
return data
})
re += JSON.stringify(result) + (i == len-1 ? "" : ",\n")
}
let json = "[\n"+re+"\n]"
console.log(chalk.green("所有數據抓取完畢:\n", json))
fs.writeFile('heros_detail.txt', json, 'utf8', function(error){
if(error){
console.log(chalk.green(error));
return false;
}
console.log(chalk.blue('數據寫入文件成功!'));
})
// await page.screenshot({
// path: './screenshot-x.png',
// type: 'png',
// // quality: 100, 只對jpg有效
// fullPage: true,
// // 指定區域截圖,clip和fullPage兩者只能設置一個
// // clip: {
// // x: 0,
// // y: 0,
// // width: 1000,
// // height: 40
// // }
// });
await browser.close()
}).catch(err => console.log(err))
以上代碼實現思路:遍歷列表頁英雄總數 -> 獲取每個被點擊進入詳情頁的dom或url -> 遍歷訪問每個詳情頁 -> 寫一個處理每個詳情頁的函數 -> 處理完每個頁面之後控制檯打印當前進度並將每個處理後的數據對象轉成json拼接爲字符串,最後處理成合法json格式的數組 -> 創建txt文件並寫入結果數據 或者 寫入數據庫
如果需要還詳細的數據,比如皮膚、英雄背景、技能介紹等,在handleData函數中處理即可
常用api:
// page.type 獲取輸入框焦點並輸入文字
// page.click()/ElemetHandle.click() 點擊一個元素
// page.keyboard.press 模擬鍵盤按下某個按鍵,目前mac上組合鍵無效爲已知bug
// page.waitFor 頁面等待,可以是時間、某個元素、某個函數
// page.frames() 獲取當前頁面所有的 iframe,然後根據 iframe 的名字精確獲取某個想要的 iframe
// page.evaluate() 在瀏覽器中執行函數,相當於在控制檯中執行函數,返回一個 Promise
// Array.from 將類數組對象轉化爲對象
// page.$$(selector) 獲取一組元素,底層調用的是 document.querySelectorAll(). 返回 Promise(Array(ElemetHandle)) 元素數組.
// page.$(selector) 獲取單個元素,底層是調用的是 document.querySelector() , 所以選擇器的 selector 格式遵循 css 選擇器規範
// page.$eval("#ele", el => el.innerHTML) 相當於在 iframe 中運行 document.queryselector 獲取指定元素,並將其作爲第一個參數傳遞
// page.$$eval(".ele", els => els.map(el => el.innerHTML)) 相當於在 iframe 中運行 document.querySelectorAll 獲取指定元素數組,並將其作爲第一個參數傳遞