google chrome團(tuán)隊(duì)出品的puppeteer 是依賴(lài)nodejs和chromium的自動(dòng)化測(cè)試庫(kù),它的最大優(yōu)點(diǎn)就是可以處理網(wǎng)頁(yè)中的動(dòng)態(tài)內(nèi)容,如JavaScript,能夠更好的模擬用戶(hù)。
有些網(wǎng)站的反爬蟲(chóng)手段是將部分內(nèi)容隱藏于某些javascript/ajax請(qǐng)求中,致使直接獲取a標(biāo)簽的方式不奏效。甚至有些網(wǎng)站會(huì)設(shè)置隱藏元素“陷阱”,對(duì)用戶(hù)不可見(jiàn),腳本觸發(fā)則認(rèn)為是機(jī)器。這種情況下,puppeteer的優(yōu)勢(shì)就凸顯出來(lái)了。
它可實(shí)現(xiàn)如下功能:
網(wǎng)站建設(shè)哪家好,找成都創(chuàng)新互聯(lián)公司!專(zhuān)注于網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、微信平臺(tái)小程序開(kāi)發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶(hù)創(chuàng)新互聯(lián)還提供了莫力達(dá)免費(fèi)建站歡迎大家使用!
開(kāi)源地址:[https://github.com/GoogleChrome/puppeteer/][1]
npm i puppeteer
注意先安裝nodejs, 并在nodejs文件根目錄下執(zhí)行(npm文件同級(jí))。
安裝過(guò)程中會(huì)下載chromium,大約120M。
用兩天(大約10小時(shí))摸索,繞過(guò)了相當(dāng)多的異步的坑,筆者對(duì)puppeteer和nodejs有了一定的掌握。
一張長(zhǎng)圖,抓取blog文章列表:
以csdn blog為例,文章內(nèi)容需要點(diǎn)擊“閱讀全文”來(lái)獲取,這就導(dǎo)致只能讀取dom的腳本失效。
/**
* load blog.csdn.net article to local files
**/
const puppeteer = require('puppeteer');
//emulate iphone
const userAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1';
const workPath = './contents';
const fs = require("fs");
if (!fs.existsSync(workPath)) {
fs.mkdirSync(workPath)
}
//base url
const rootUrl = 'https://blog.csdn.net/';
//max wait milliseconds
const maxWait = 100;
//max loop scroll times
const makLoop = 10;
(async () => {
let url;
let countUrl=0;
const browser = await puppeteer.launch({headless: false});//set headless: true will hide chromium UI
const page = await browser.newPage();
await page.setUserAgent(userAgent);
await page.setViewport({width:414, height:736});
await page.setRequestInterception(true);
//filter to block images
page.on('request', request => {
if (request.resourceType() === 'image')
request.abort();
else
request.continue();
});
await page.goto(rootUrl);
for(let i= 0; iwindow.scrollTo(0, document.body.scrollHeight));
await page.waitForNavigation({timeout:maxWait,waitUntil: ['networkidle0']});
}catch(err){
console.log('scroll to bottom and then wait '+maxWait+'ms.');
}
}
await page.screenshot({path: workPath+'/screenshot.png',fullPage: true, quality :100, type :'jpeg'});
//#feedlist_id li[data-type="blog"] a
const sel = '#feedlist_id li[data-type="blog"] h3 a';
const hrefs = await page.evaluate((sel) => {
let elements = Array.from(document.querySelectorAll(sel));
let links = elements.map(element => {
return element.href
})
return links;
}, sel);
console.log('total links: '+hrefs.length);
process();
async function process(){
if(countUrl {
if (request.resourceType() === 'image')
request.abort();
else
request.continue();
});
await tab.goto(url);
//execute tap request
try{
await tab.tap('.read_more_btn');
}catch(err){
console.log('there\'s none read more button. No need to TAP');
}
let title = await tab.evaluate(() => document.querySelector('#article .article_title').innerText);
let contents = await tab.evaluate(() => document.querySelector('#article .article_content').innerText);
contents = 'TITLE: '+title+'\nURL: '+url+'\nCONTENTS: \n'+contents;
const fs = require("fs");
fs.writeFileSync(workPath+'/'+tab.url().substring(tab.url().lastIndexOf('/'),tab.url().length)+'.txt',contents);
console.log(title + " has been downloaded to local.");
await tab.close();
}catch(err){
console.log('url: '+tab.url()+' \n'+err.toString());
}finally{
process();
}
}
})();
錄屏可以在我公眾號(hào)查看,下邊是截圖:
文章內(nèi)容列表:
文章內(nèi)容:
以前就想過(guò)既然nodejs是使用JavaScript腳本語(yǔ)言,那么它肯定能處理網(wǎng)頁(yè)的JavaScript內(nèi)容,但并沒(méi)有發(fā)現(xiàn)合適的/高效率的庫(kù)。直到發(fā)現(xiàn)puppeteer,才下定決心試水。
話說(shuō)回來(lái),nodejs的異步真的是很頭疼的一件事,這上百行代碼我竟然折騰了10個(gè)小時(shí)。
大家可拓展下代碼中process()
方法,使用async.eachSeries
,我使用的遞歸方式并不是最優(yōu)解。
事實(shí)上,逐一處理并不高效,原本我寫(xiě)了一個(gè)異步的關(guān)閉browser方法:
let tryCloseBrowser = setInterval(function(){
console.log("check if any process running...")
if(countDown<=0){
clearInterval(tryCloseBrowser);
console.log("none process running, close.")
browser.close();
}
},3000);
按照這個(gè)思路,代碼的最初版本是同時(shí)打開(kāi)多個(gè)tab頁(yè),效率很高,但容錯(cuò)率很低,大家可以試著自己寫(xiě)一下。
看過(guò)我的文章的人都知道,我寫(xiě)文章更強(qiáng)調(diào)處理問(wèn)題的方式/方法,給大家一些思維上的建議。
對(duì)于nodejs和puppeteer我是完全陌生的(當(dāng)然,我知道他們適合做什么,僅此而已)。如果大家還記得《10倍速程序員》里提到的按需記憶的理念,那么你就會(huì)理解我刻意的不去系統(tǒng)的學(xué)習(xí)新技術(shù)。
我說(shuō)說(shuō)我接觸puppeteer到完成我需要功能的所有思維邏輯:
2018年5月9日02點(diǎn)13分