Puppeteer爬取单页面网站的数据示例

时间:2024-03-05 15:35:49

场景

  • 昨天试了一下爬取根据网页查询参数的不同而变化的页面,今天来试试爬取单页面应用,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对于爬取这种以前很难爬取的单页面应用来说,确实提供了不少便利。