hanson影院全棧開發日誌之Puppeteer爬蟲實踐

Mr_Hanson發表於2019-03-17

前言

puppeteer是google開源的一個基於headless chrome的node.js爬蟲框架,與傳統py爬蟲框架不同的在於puppeteer跟貼近於前端開發者的習慣

目的

爬取豆瓣電影的電影資訊並寫入MongoDb資料庫,目標頁面

坑點

一、 目標頁面的電影列表採用服務端渲染非同步載入的方式

  • 分析Dom結構後,初步推斷可以通過#content > div > div.article > div > div.list-wp > div > a:nth-child(1)選擇器獲取該頁面0-20條的電影資料,於是我決定這樣寫
  const ListDom = await page.$(
    "#content > div > div.article > div > div.list-wp > div.list"
  );
  movieList = await page.$$eval(".item .cover-wp", els =>
    els.map(el => {
      return {
        id: el.dataset.id
      };
    })
  );
複製程式碼

但這樣的操作需要在等待dom完全載入完成後,才能進行

  • 解決方案:
  1. 加上page.waitFor(2000s) 或者 監聽page.on('load',()=>{ //code}進行操作,但若需要爬取更多資料時,page.waitFor(2000s)的方案太過耗時和消耗記憶體,若將爬蟲指令碼部署後,等待的時間由於網路問題並不能精確地把控。至於監聽load事件在有一定機率會失效
  2. 基於以上考慮最後還是老實的使用douban的Api,通過更改queryString Parameters的page_limit 和 page_start引數可以按需獲取電影列表概要資訊。當然在數次debug之後,最終免不了被封ip
    hanson影院全棧開發日誌之Puppeteer爬蟲實踐

二、當async遇上for迴圈

  /**通過處理後得的電影物件資料結構如下
   * Data structure of Movie
   * {
   *   name: 'xxx',
   *   href: 'www.xx.com',
   *   year: 2018,
   *   directors: [{
   * 	   name: 'xxx',
   *       url: 'http://...'
   * 	 }],
   *   actors: [{
   * 	   name: 'xxx',
   *       url: 'http://...'
   * 	 }],
   *   type: [],
   *   origin: '中國',
   *   language: '普通話',
   *   discription: 'xxxxx',
   * }  */
複製程式碼

現在需要提取directors和actors陣列中的資料並獲取更詳盡的資訊,所以開始遍歷movieArr陣列

// castCollection
let castArr = new Array();

for (let i = 0; i < movieArr.length; i++) {
  let castList = await getCastList(browser, movieArr[i]);
  castArr = castArr.concat(castList);
}

return castArr;
複製程式碼

但以上程式碼結果並不符合預期,想要是for迴圈內非同步程式碼序列執行,而實際結果則是並行執行,於是有了以下改寫,達到預期目的

// castCollection
const castArr = await (async () => {
    let castArr = new Array();
    
    for (let i = 0; i < movieArr.length; i++) {
      let castList = await getCastList(browser, movieArr[i]);
      castArr = castArr.concat(castList);
    }
    
    return castArr;
})();
複製程式碼

三、物件陣列去重

  • 有了電影物件的Collection和演職人物件的Collection後,就差電影標籤了,但需要對標籤陣列的元素進行去重。
  • 解決方案
  1. 最簡單使用ES6的新資料結構Set進行去重const typeSet = new Set(typeArr)
  2. 利用輔助物件雜湊去重
// 這裡使用type物件的id屬性作為唯一識別符號
let hash = {};
let typeSet = typeArr.filter((element, index) => {
  if (typeof hash[element.id] === "undefined") {
    hash[element.id] = index;
  }
  return hash[element.id] === index;
});
複製程式碼

相關文章