前言
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完全載入完成後,才能進行
- 解決方案:
- 加上
page.waitFor(2000s)
或者 監聽page.on('load',()=>{ //code}
進行操作,但若需要爬取更多資料時,page.waitFor(2000s)
的方案太過耗時和消耗記憶體,若將爬蟲指令碼部署後,等待的時間由於網路問題並不能精確地把控。至於監聽load事件在有一定機率會失效 - 基於以上考慮最後還是老實的使用douban的Api,通過更改queryString Parameters的page_limit 和 page_start引數可以按需獲取電影列表概要資訊。當然在數次debug之後,最終免不了被封ip
二、當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後,就差電影標籤了,但需要對標籤陣列的元素進行去重。
- 解決方案
- 最簡單使用ES6的新資料結構Set進行去重
const typeSet = new Set(typeArr)
- 利用輔助物件雜湊去重
// 這裡使用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;
});
複製程式碼