Puppeteer 爬蟲進階 爬取豆瓣電影詳情內容

Puppeteer 是什麼

  • Puppeteer 是 Node.js 工具引擎
  • Puppeteer 提供了一系列 API,經過 Chrome DevTools Protocol 協議控制 Chromium/Chrome 瀏覽器的行爲
  • Puppeteer 默認狀況下是以 headless 啓動 Chrome 的,也能夠經過參數控制啓動有界面的 Chrome
  • Puppeteer 默認綁定最新的 Chromium 版本,也能夠本身設置不一樣版本的綁定
  • Puppeteer 讓咱們不須要了解太多的底層 CDP 協議實現與瀏覽器的通訊

Puppeteer 能作什麼

官方稱:「Most things that you can do manually in the browser can be done using Puppeteer」,那麼具體能夠作些什麼呢?javascript

  1. 網頁截圖或者生成 PDF
  2. 爬取 SPA 或 SSR 網站
  3. UI 自動化測試,模擬表單提交,鍵盤輸入,點擊等行爲
  4. 捕獲網站的時間線,幫助診斷性能問題
  5. 建立一個最新的自動化測試環境,使用最新的 js 和最新的 Chrome 瀏覽器運行測試用例
  6. 測試 Chrome 擴展程序 ...

Puppeteer 經常使用API

  • Browser: 對應一個瀏覽器實例,一個 Browser 能夠包含多個 BrowserContext
  • BrowserContext: 對應瀏覽器一個上下文會話,就像咱們打開一個普通的 Chrome 以後又打開一個隱身模式的瀏覽器同樣,BrowserContext 具備獨立的 Session(cookie 和 cache 獨立不共享),一個 BrowserContext 能夠包含多個 Page
  • Page:表示一個 Tab 頁面,經過 browserContext.newPage()/browser.newPage() 建立,browser.newPage() 建立頁面時會使用默認的 BrowserContext,一個 Page 能夠包含多個 Frame
  • Frame: 一個框架,每一個頁面有一個主框架(page.MainFrame()),也能夠多個子框架,主要由 iframe 標籤建立產生的
  • ExecutionContext: 是 javascript 的執行環境,每個 Frame 都一個默認的 javascript 執行環境
  • ElementHandle: 對應 DOM 的一個元素節點,經過該該實例能夠實現對元素的點擊,填寫表單等行爲,咱們能夠經過選擇器,xPath 等來獲取對應的元素
  • JsHandle:對應 DOM 中的 javascript 對象,ElementHandle 繼承於 JsHandle,因爲咱們沒法直接操做 DOM 中對象,因此封裝成 JsHandle 來實現相關功能
  • CDPSession:能夠直接與原生的 CDP 進行通訊,經過 session.send 函數直接發消息,經過 session.on 接收消息,能夠實現 Puppeteer API 中沒有涉及的功能
  • Coverage:獲取 JavaScript 和 CSS 代碼覆蓋率
  • Tracing:抓取性能數據進行分析
  • Response: 頁面收到的響應
  • Request: 頁面發出的請求

更多請查閱官方文檔 github地址java

準備工做

安裝模塊

# 安裝puppeteer
npm install puppeteer --save
# puppeteer 須要安裝 chromium,能夠選擇修改 puppeteer 的下載源:
npm config set puppeteer_download_host https://npm.taobao.org/mirrors

# 安裝chalk
npm install chalk --save
複製代碼

先思考

咱們能夠先去觀察下這個 頁面HTML結構, 爬取這個頁面以及每一個詳情頁面咱們須要什麼git

  1. 爬取頁面所須要的頁面元素
  2. 跟頁面的交互(好比分頁)
  3. 該怎麼樣獲取多頁內容
  4. 怎樣跳到詳情頁面 ...

基本思路

  1. 打開咱們要爬取的頁面
  2. 獲取數據列表以及分頁按鈕
  3. 循環列表打開每個item
  4. 在頁面中執行js拿到咱們想要拿到的數據
  5. 當前頁數據抓取完畢點擊下一頁按鈕 ...

爬取豆瓣電影

async index() {
    const { ctx } = this;
    const log = console.log;
    /// 圖方便我把經常使用的數據寫在了這裏
    const scrape = {
      url: 'https://movie.douban.com/tag/#/',
      click: '.more',
      page: 2,
      itemList: '.list-wp > a',
    };
    /// 啓動一個瀏覽器環境
    const browser = await puppeteer.launch();
    log(chalk.green('服務正常啓動'));
    /// 捕獲錯誤
    try {
      /// 打開一個新的頁面
      const page = await browser.newPage();
      /// 咱們須要監聽 request的話 必需要開啓
      await page.setRequestInterception(true);
      /// 監聽內部的request
      page.on('request', interceptedRequest => {
        if (interceptedRequest.url().endsWith('.png') || interceptedRequest.url().endsWith('.jpg'))
		  interceptedRequest.abort();
		else
		  interceptedRequest.continue();
      });
      /// 打開一個頁面 config 請自行查閱文檔
      await page.goto(scrape.url, {
        waitUntil: 'networkidle2',
        timeout: 0,
      });
      
      /// scrape.page 爬取幾頁內容
      for (let l = 0; l < scrape.page; l++) {
        /// 等待頁面元素加載完畢
        await page.waitForSelector(scrape.click);
        if (l > 0) {
          const submit = await page.$(scrape.click);
          if (!submit) {
            log(chalk.red('按鈕不存在!'));
            return;
          }
          await submit.click();
          await page.waitFor(2500);
        }
        console.clear();
        /// 格式化進度
        log(chalk.yellow(ctx.helper.formatProgress(ctx, l, scrape.page)));
        /// 獲取當前列表
        const itemList = await page.$$(scrape.itemList);
        // 開始循環當前列表 咱們看到列表一頁數據在20條 因此咱們從當前頁面*20開始循環 這樣就不會出現每次從新循環都從第一條數據開始
        for (let i = l * 20; i < itemList.length; i++) {
          const items = await page.$$(scrape.itemList);
          const item = items[i];
          /// 獲取列表的第i個元素的url
          const url = await page.evaluate(item => {
            return item ? item.href : '';
          }, items[i]);
          if (url) {
            const page2 = await browser.newPage();
            await page2.goto(url, {
              timeout: 0,
            });
            /// 等待一會
            await page2.waitFor(1000);
            
            /// page2.evaluate 使咱們能夠在頁面中執行js方法
            const result = await page2.evaluate(() => {
              const info = document.querySelector('#info');
              const screenwriter = [];
              const starring = [];
              const types = [];
               info.querySelectorAll('.attrs')[1].querySelectorAll('a').forEach(item => {
                screenwriter.push(item.innerText);
              });
              info.querySelectorAll('.attrs')[2].querySelectorAll('a').forEach(item => {
                starring.push(item.innerText);
              });
              info.querySelectorAll('[property="v:genre"]').forEach(item => {
                types.push(item.innerText);
              });
				
              return {
                title: document.querySelector('h1') ? document.querySelector('h1').innerText : '',
                mainpic: document.querySelector('.nbgnbg > img') ? document.querySelector('.nbgnbg > img').src : '',
                director: info.querySelectorAll('.attrs')[0].querySelector('a') ? info.querySelectorAll('.attrs')[0].querySelector('a').innerText : '',
                screenwriter,
                starring,
                types,
                release_date: info.querySelector('[property="v:initialReleaseDate"]') ? info.querySelector('[property="v:initialReleaseDate"]').innerText : '',
                length: info.querySelector('[property="v:runtime"]') ? info.querySelector('[property="v:runtime"]').innerText : '',
                rating_num: document.querySelector('[property="v:average"]') ? document.querySelector('[property="v:average"]').innerText : '',
              };
            });
            
            /// 寫入文件
            fs.appendFile('./movie.json', JSON.stringify(result, null, '\t', {
              flag: 'a',
            }), function(err) {
              if (err) {
                throw err;
              }
            });

          }
        }
      }
      await browser.close();
      log(chalk.green('服務正常結束'));
    } catch (error) {
      console.log(error);
      log(chalk.red('服務意外終止'));
      await browser.close();
    }
  }
複製代碼

爬取的內容

{
	"title": "致命女人 第一季 Why Women Kill Season 1 (2019)",
	"mainpic": "https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2566967861.webp",
	"director": "大衛·格羅斯曼",
	"screenwriter": [
		"馬克·切利",
		"艾莉莎·榮格"
	],
	"starring": [
		"劉玉玲",
		"金妮弗·古德溫",
		"柯爾比·豪威爾-巴普蒂斯特",
		"傑克·達文波特",
		"山姆·賈格",
		"裏德·斯科特",
		"亞歷珊德拉·達達里奧",
		"賽迪·卡爾瓦諾",
		"里奧·霍華德",
		"艾麗莎·科波拉",
		"凱蒂·芬內朗",
		"更多..."
	],
	"types": [
		"劇情",
		"喜劇",
		"犯罪"
	],
	"release_date": "2019-07-21(洛杉磯LGBT電影節)",
	"length": "",
	"rating_num": "9.4"
}
複製代碼

結尾

無事擼一擼,擼的不對勿噴,你們一塊兒學習一塊兒成長 Come ongithub

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息