1. 程式人生 > 實用技巧 >Puppeteer爬取單頁面網站的資料示例

Puppeteer爬取單頁面網站的資料示例

場景

  • 昨天試了一下爬取根據網頁查詢引數的不同而變化的頁面,今天來試試爬取單頁面應用,url不發生變化,只是頁面內的按鈕點選導致資料的重新請求。

主要實現思路

  • 利用Puppeteer可以模擬使用者點選操作,等待介面返回等各種優秀的API,可以保證在資料結束後完成頁面資料提取。

程式碼實現,以開源眾包的頁面為例

  • 開源眾包這個頁面挺適合用來做示例,因為通過下一頁的按鈕去呼叫ajax請求,當到達最後一頁時,下一頁按鈕會自動有一個disabled屬性,我們就可以根據這個disabled屬性來判斷是否還有下一頁。
const common = async (workFunc) => {
  const startTime = +new Date();
  console.log(`進入方法`);
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  typeof workFunc === 'function' && await workFunc(page);
  await page.close();
  await browser.close();
  console.log('方法結束,耗費時長:', +new Date() - startTime);
};
const crawler = async (url, selectors) => {
  const list = [];
  await common(async (page) => {
    await page.goto(url);
    async function runOnce() {
      const result = await page.evaluate((selectors) => {
        const res = [];
        selectors.forEach(selector => {
          const { key, value, field } = selector;
          const domList = document.querySelectorAll(value);
          Array.prototype.slice.apply(domList).forEach((dom, index) => {
            const newVal = dom[field] || dom.innerText;
            res[index] = res[index] || {};
            res[index][key] = newVal;
          })
        })
        return res;
      }, selectors);
      list.push(...result);
      const disabled = await page.$eval('.btn-next', el => el.disabled);
      console.log('一頁資料已獲取,當前下一頁按鈕狀態:', result[0], disabled);
      if (!disabled) {
        // 觸發按鈕點選,使用page.click就是不能觸發按鈕的點選,只能用這個騷操作
        await page.$eval('.btn-next', el => el.click());
        // 等待介面完成
        await page.waitForResponse(response => {
          return response.url().includes('contractor-browse-project-and-reward') && response.status() === 200;
        })
        await runOnce();
      }
    }
    await runOnce();
  })
  return list;
}

// 使用
const url = 'https://zb.oschina.net/projects/list.html';
const selectors = [
  {
    key: 'title',
    value: '.el-row .title',
    field: 'innerText'
  },
  {
    key: 'tags',
    value: '.el-row .tags',
    field: 'innerText'
  },
  {
    key: 'money',
    value: '.el-row .money',
    field: 'innerText'
  },
  {
    key: 'skills',
    value: '.el-row .skills',
    field: 'innerText'
  },
  {
    key: 'bidding',
    value: '.el-row .bidding',
    field: 'innerText'
  },
  {
    key: 'pubtime',
    value: '.el-row .pubtime',
    field: 'innerText'
  }
];

crawler(url, selectors).then(result => {
  console.log(result);
  fs.writeFile('專案.json', JSON.stringify(result), 'utf-8', err => {
    if(err) {
      console.log(err);
      return;
    }
  });
})

結果展示

-

小結

  • 不得不承認,Puppeteer對於爬取這種以前很難爬取的單頁面應用來說,確實提供了不少便利。