google chrome團隊出品的puppeteer 是依賴nodejs和chromium的自動化測試庫,它的最大優勢就是能夠處理網頁中的動態內容,如JavaScript,可以更好的模擬用戶。
有些網站的反爬蟲手段是將部份內容隱藏於某些javascript/ajax請求中,導致直接獲取a標籤的方式不奏效。甚至有些網站會設置隱藏元素「陷阱」,對用戶不可見,腳本觸發則認爲是機器。這種狀況下,puppeteer的優點就凸顯出來了。
它可實現以下功能:javascript
開源地址:https://github.com/GoogleChro...java
npm i puppeteer
注意先安裝nodejs, 並在nodejs文件根目錄下執行(npm文件同級)。
安裝過程當中會下載chromium,大約120M。node
用兩天(大約10小時)摸索,繞過了至關多的異步的坑,筆者對puppeteer和nodejs有了必定的掌握。
一張長圖,抓取blog文章列表:
git
以csdn blog爲例,文章內容須要點擊「閱讀全文」來獲取,這就致使只能讀取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; i<makLoop;i++){ try{ await page.evaluate(()=>window.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"] h2 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<hrefs.length){ url = hrefs[countUrl]; countUrl++; }else{ browser.close(); return; } console.log('processing url: '+url); try{ const tab = await browser.newPage(); await tab.setUserAgent(userAgent); await tab.setViewport({width:414, height:736}); await tab.setRequestInterception(true); //filter to block images tab.on('request', request => { 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(); } } })();
錄屏能夠在我公衆號查看,下邊是截圖:github
文章內容列表:ajax
文章內容:chrome
之前就想過既然nodejs是使用JavaScript腳本語言,那麼它確定能處理網頁的JavaScript內容,但並無發現合適的/高效率的庫。直到發現puppeteer,才下定決心試水。
話說回來,nodejs的異步真的是很頭疼的一件事,這上百行代碼我居然折騰了10個小時。
你們可拓展下代碼中process()
方法,使用async.eachSeries
,我使用的遞歸方式並非最優解。
事實上,逐一處理並不高效,本來我寫了一個異步的關閉browser方法:npm
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);
按照這個思路,代碼的最第一版本是同時打開多個tab頁,效率很高,但容錯率很低,你們能夠試着本身寫一下。api
看過個人文章的人都知道,我寫文章更強調處理問題的方式/方法,給你們一些思惟上的建議。
對於nodejs和puppeteer我是徹底陌生的(固然,我知道他們適合作什麼,僅此而已)。若是你們還記得《10倍速程序員》裏提到的按需記憶的理念,那麼你就會理解我刻意的不去系統的學習新技術。
我說說我接觸puppeteer到完成我須要功能的全部思惟邏輯:
2018年5月9日02點13分