從零開始,如何用puppeteer寫一個爬蟲指令碼

melor發表於2018-08-17

最近看到一篇關於爬蟲的文章,而自己又正好在爬蟲,於是就想寫一篇分享下, 讓我們一步一步來,

第一步:安裝核心爬蟲依賴puppeteer, 如果你開啟googole.com是404,執行npm i puppeteer前,先執行set PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1; ok,如果沒有問題,我們可以在專案根目錄(下文簡稱根目錄)下新建index.js;

//index.js
const puppeteer=require('puppeteer');
複製程式碼

第二步:選擇一個你需要爬取資源的站點, 作為一個b站使用者,拿b站作為舉例吧,我經常會看排行榜,於是今天我們就爬爬排行榜,地址(www.bilibili.com/ranking/all…

第三步:分析如何爬取站點內容, 開啟chrome瀏覽器,按f12,按ctrl+shift+c,你首先就會看到排行榜每個條目對應的一些資訊,如果你有過簡單的爬蟲經驗,你大概可能會直接獲取頁面內容再做提取,當然不是不行,但這種方法一般最後才採取。更優雅的方法是去爬api,要爬api,我們需要將剛剛開啟的除錯工具切換到network介面,在頁面點一點連結跳轉,你會發現一些請求記錄,自己點開看看,那些是載入這個網頁所需要的資源,其中可能就有我們需要的資源,一般來說,xhr選項卡會對應資料內容,但比較神奇,經過除錯發現,資料再js選項卡中找到。

從零開始,如何用puppeteer寫一個爬蟲指令碼
然後重新整理一下頁面,嗯,你會發現,沒有了剛剛的發現請求,那資料去哪了呢,api沒資料,那資料只能在頁面了,估計是為了更好的渲染,資料放在服務端渲染了,api資料只有在切換頁面的時候才會有,那麼這次就直接用最後手段在頁面直接爬取吧。

第四步:寫爬取程式碼, 回到我們的index.js

//index.js
const puppeteer=require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');
const iPad = devices['iPad landscape'];//https://github.com/GoogleChrome/puppeteer/blob/master/DeviceDescriptors.js

const program = require('commander');

//定義一些命令
program
    .version('0.0.1')
    .option('-t, --top_10','show top 10')
    .parse(process.argv);

//記錄結果,如果想寫到資料庫,自行對接即可
const log4js = require('log4js');
log4js.configure({
    appenders: { log: { type: 'file', filename: './log/log.log' } },
    categories: { default: { appenders: ['log'], level: 'info' } }
});
const logger = log4js.getLogger('log');

const ifOpenBrowser=false;
const lanchConf={
    headless:!ifOpenBrowser,
    // executablePath: 'C:/Users/xxx/Downloads/chromium/chrome-win32/chrome.exe',//mac使用者自行檢視文件更改
};

const sleep=(time)=>{
    return new Promise(resolve=>setTimeout(resolve,time))
};
async function repeat(time,fn,gapTime=500){
    if(time>0){
        // console.log('do fn',time);
        await fn();
        await sleep(gapTime);
        return repeat(--time,fn,gapTime)
    }
    // console.log('final');
    return {msg:'done'}
}
const banList=['.png','.jpg'];
puppeteer.launch(lanchConf).then(async browser => {
    //開啟一個新的頁面
    const page = await browser.newPage();
    //更改瀏覽器外觀,寬高等
    await page.emulate(iPad);
    //啟用請求攔截
    await page.setRequestInterception(true);
    //攔截無用請求
    page.on('request', interceptedRequest => {
        //遮蔽字尾為.png或.jpg的請求;減少資源消耗
        if (banList.some(val=>interceptedRequest.url().match(val))){
            interceptedRequest.continue();
			//本來是要遮蔽的,但圖片地址在遮蔽的情況下不能獲取,故開啟
        } else{
            interceptedRequest.continue();
        }
    });
    //跳轉到我們的目標頁面
    await page.goto('https://www.bilibili.com/ranking/all/0/0/3',{
        waitUntil:'networkidle0'//頁面完全載入
    });

    // 圖片實現了懶載入,頁面需要滾動到底部,連續點選翻頁鍵一定次數,否則圖片地址可能不能拿到
    await repeat(20,async ()=>{
        await page.keyboard.press('PageDown',200);
    },200);

    //通過選擇器找到目標,如果覺得api難懂,建議使用cheerio輔助
    const listHandle = await page.$('.rank-list');
    //處理子節點內容,難點在選擇器的處理,部分反爬蟲的頁面不會提供一直不變的選擇器
    const titles=await listHandle.$$eval('.info .title', nodes => nodes.map(n => n.innerText));
    const authors=await listHandle.$$eval('.detail>a>.data-box', nodes => nodes.map(n => n.innerText));
    const pts=await listHandle.$$eval('.pts div', nodes => nodes.map(n => n.innerText));
    const links=await listHandle.$$eval('.title', nodes => nodes.map(n => n.getAttribute('href')));
    const views=await listHandle.$$eval('.detail>.data-box', nodes => nodes.map(n => n.innerText));
    const images=await listHandle.$$eval('img', nodes => nodes.map(n => n.getAttribute('src')));

    //序列化結果
    const res=[];
    for(let i=0;i<100;i++){
        res[i]={
            rank:i+1,
            title:titles[i],
            author:authors[i],
            pts:pts[i],
            link:links[i],
            view:views[i],
            image:images[i]
        }
    }
    
    //根據命令列輸出資料
    if (program.top_10) console.log(res.slice(0,10));
    //寫入資料
    logger.info(res);
    //關閉瀏覽器
    await browser.close();
});
複製程式碼

寫入上面的內容,認真瀏覽閱讀相關配置選項,然後補全相關依賴npm i commander log4js; 開啟當前專案所在位置的命令列介面,輸入node .程式執行的結果就會輸出到根目錄下的log目錄中,如果想在命令列檢視前10條資料,可以執行node . -tnode . -top_10即可,

第五步,上傳程式碼到github,

ps:如果執行了set PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1,需設定本地chromium路徑。另外,如果使用cnpm安裝依賴,chromium似乎能正常下載下來,不妨試試

相關文章