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

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息