学习puppeteer也有一段时间了,不得不说puppeteer确实是一个非常强大的工具,今天将实现最后一个小案例:使用puppeteer爬取微博热搜并配合node-schedule实现定时爬取的功能。实现效果参考本站右侧菜单栏。

相关前置文章:
使用node-schedule模块实现Node.js定时任务管理
Node.js使用puppeteer+axios爬取页面图片链接并下载图片到本地
Node.js使用puppeteer+axios爬取页面图片链接并下载图片到本地

1.puppeteer-core与puppeteer

这里为什么首先要提出puppeteer-core这个新的模块呢?其实是因为在服务器端开发时puppeteer会因为兼容性问题不太好用,因此我们可以使用puppeteer-core配合本地的Chrome来使用。
简单来说puppeteer = puppeteer-core + Chrome。

2.服务器下载Chrome

由于使用的是puppeteer-core,需要手动下载Chrome。
以centos系统为例,使用如下指令安装:sudo yum install chromium

3.打开浏览器并设置为无头模式

由于在服务器端并不需要可视化界面,因此可以将headless设置为true。要注意的是puppeteer-core中需要手动配置Chrome的路径:

const weiboHotUrl = "https://s.weibo.com/top/summary?cate=realtimehot";  // 微博链接
const browser = await puppeteer.launch({
	  headless: true,
// 	  executablePath: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",  // Windows操作系统下chrome安装路径
	  executablePath: '/usr/bin/chromium-browser',         // Linux操作系统下chrome安装路径
	  args: ['--no-sandbox'] // 设置其他参数 禁止沙盒模式
	});
const page = await browser.newPage();
await page.goto(weiboHotUrl);

4.分析微博页面dom结构

打开浏览器开发者工具,在页面结构中提取出微博热搜相关dom结构,只要是能准确定位到所需要的dom结构即可。

await page.waitForSelector('#pl_top_realtimehot tbody>tr .td-02');  // 等待此标签的渲染
const tds = await page.$$('#pl_top_realtimehot tbody>tr .td-02');   // 获取到dom集合数组

5.整理所需要的数据

const list = await Promise.all(tds.slice(1).map(async (td, i) => {  // 数组第一项不是热搜内容
	 const list1 = await td.$eval('a', a => ({
	   title: a.innerText,  // 标题
	   url: `https://s.weibo.com${a.getAttribute('href')}`,   // 链接
	 }));
	 const list2 = await td.$eval('span', span => ({
	   hot: span.innerText  // 热度
	 }));
	 return {
	   id: i + 1,
	   ...list1,
	   ...list2,
	 };
})); 

6.封装成函数

// 爬取微博热搜
const get_weibo_hot=async ()=>{
	const weiboHotUrl = "https://s.weibo.com/top/summary?cate=realtimehot";
	const browser = await puppeteer.launch({
	headless: true,
// 	executablePath: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
	   executablePath: '/usr/bin/chromium-browser',
	   args: ['--no-sandbox'] 
	});
	try {
          const page = await browser.newPage();
	  await page.goto(weiboHotUrl);
	  await page.waitForSelector('#pl_top_realtimehot tbody>tr .td-02');
	  const tds = await page.$$('#pl_top_realtimehot tbody>tr .td-02');
	  const list = await Promise.all(tds.slice(1).map(async (td, i) => {
	    const list1 = await td.$eval('a', a => ({
	      title: a.innerText,
	      url: `https://s.weibo.com${a.getAttribute('href')}`,
	    }));
	    const list2 = await td.$eval('span', span => ({
	      hot: span.innerText
	    }));
	    return {id: i + 1,...list1,...list2};
	  })); 
	  return list;
	} catch (e) {
	  console.error(e);
	} finally {
	  await browser.close();  // 关闭浏览器避免服务器资源的浪费
	}
}

7.设置定时任务

// 每5分钟执行一次  爬取微博热搜
schedule.scheduleJob('*/5 * * * *', async ()=>{
    const res=await get_weibo_hot();
    await redis.set('weibo_hot',JSON.stringify(res));  // 把数据存入redis
});