浅浅的记录下puppeteer使用

背景

实现前端页面自动生成截图功能,如订阅页面数据场景,以页面截图方式推送

项目初始化

js 复制代码
npm init

puppeteer安装

js 复制代码
// 未安装pm2需先全局安装pm2
npm install pm2 -g
// 依赖express启动服务 compression压缩 puppeteer完成截图
// puppeteer 会自动安装chrome无头浏览器,版本不同,安装逻辑不同
npm install express compression puppeteer

项目启动配置

根目录新建ecosystem.config.js

js 复制代码
module.exports = {
  apps: [
    {
      name: 'project-name',
      script: './app.js', // 启动执行文件
      max_memory_restart: '200M', // 内存超800M重启,因为无头浏览器占用内存较大,防止同机器上其他应用崩溃,设置重启
      watch: false,
      error_file: './pm2-error.log', // 错误日志收集
      out_file: './pm2-out.log', // 访问日志收集
      log_date_format: 'YYYY-MM-DD HH:mm:ss',
      exec_mode: "cluster", // 运行模式,可选 cluster, fork
      env: {
        PORT: 3000,
        NODE_ENV: 'development',
      },
      env_production: {
        PORT: 3000,
        NODE_ENV: 'production',
      },
    }
  ]
};

修改package.json启动脚本

json 复制代码
"scripts": {
    "serve": "pm2 start ecosystem.config.js --env development",
    "serve:prod": "pm2 start ecosystem.config.js --env production",
    "stop": "pm2 stop all",
    "delete": "pm2 delete all"
},

核心代码

根目录新建app.js

js 复制代码
const express = require('express');
const puppeteer = require('puppeteer');
const compression = require('compression');

const app = express();
const port = process.env.PORT || 3000;

// 启用 Gzip 压缩
app.use(compression());

// 最大并发数
const MAX_CONCURRENT = 5;
// 当前活动任务数
let activeCount = 0;
// 请求队列
const requestQueue = [];

// Puppeteer 页面处理逻辑
const handlePageScreenshot = async () => {
  const targetURL = process.env.WEB_URL // 目标url,可固定配置,也可根据请求动态改变;

  let browser, page;

  try {
    // 启动浏览器
    browser = await puppeteer.launch({
      args: ['--no-sandbox', '--disable-setuid-sandbox'],
    });

    // 创建页面
    page = await browser.newPage();

    // 调整视口规范
    await page.setViewport({
      width: 1280,
      height: 720,
      deviceScaleFactor: 2
    });

    // 导航到目标 URL,并等待页面加载完成,包括页面所有api请求,networkidle0是最严格的条件,确保页面所有异步请求全部完成
    // timeout: 设置超时,0 禁用超时
    await page.goto(targetURL, { waitUntil: 'networkidle0', timeout: 0 });
    console.log('成功导航到目标URL')

    // waitForFunction 可自定义等待条件
    await page.waitForFunction(() => {}, { timeout: 0 });
    console.log('所有api请求完成')

    // 可选等待 某特定DOM 元素加载完成
    await page.waitForSelector('#selector', { timeout: 0 });
    console.log('dom加载完成')

    // Puppeteer 核心方法,可修改目标元素样式等所有js逻辑,并返回到node环境中,比如存在滚动条会导致页面截图不完整,此时需要人工介入调整元素样式
    await page.evaluate(({ selector, needCountWidthSelector }) => {
      // js处理逻辑
    }, { selector, needCountWidthSelector });

    // 获取处理完之后的目标dom
    const element = await page.$(selector);
    if (!element) {
      throw new Error('目标元素未找到');
    }
    // 目标盒子
    const boundingBox = await element.boundingBox();

    // 如果页面存在动画,需延时等待动画完成,延迟时间根据实际情况而定
    await delay(1000);

    // 截图保存为 Base64
    const screenshotBase64 = await page.screenshot({
      encoding: 'base64',
      clip: {
        x: boundingBox.x,
        y: boundingBox.y,
        width: boundingBox.width,
        height: boundingBox.height,
      },
    });

    return screenshotBase64;
  } catch (error) {
    console.error('截图处理失败:', error);
    throw error; // 将错误抛出以供上层捕获
  } finally {
     // 截图完成,关闭浏览器,释放内存
    if (page) await page.close();
    if (browser) await browser.close();
  }
};

// 队列任务处理函数,防止启动浏览器数量太多,内存撑爆
const processTask = async (task) => {
    const screenshotBase64 = await handlePageScreenshot({ projectId, reportId });

    res.send(screenshotBase64);
    console.log('截图成功并返回');
  } catch (error) {
    console.error('任务处理出错:', error);
    res.status(500).send('服务器内部错误');
  } finally {
    activeCount--; // 减少活动任务计数
    processNext(); // 处理队列中的下一个任务
  }
};

// 处理队列中的下一个任务
const processNext = () => {
  console.log(`开始处理队列: 当前队列长度 ${requestQueue.length}, 活动任务数 ${activeCount}`);
  while (activeCount < MAX_CONCURRENT && requestQueue.length > 0) {
    const nextTask = requestQueue.shift(); // 从队列中取出任务
    activeCount++;
    processTask(nextTask); // 执行任务
  }
};

// API 路由
app.get('/getPage', (req, res) => {
  // 将任务加入队列
  requestQueue.push({ req, res });
  processNext(); // 尝试处理队列中的任务
});

// 启动服务
app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

注意事项

  • puppeteer 版本,及浏览器安装方式一级版本适配问题
  • 服务器需安装字体包,识别中文,避免截图乱码
相关推荐
亿牛云爬虫专家4 个月前
使用 Puppeteer 绕过 Captcha:实现商家数据自动化采集
自动化·爬虫代理·验证码·puppeteer·代理ip·大众点评·captcha
亿牛云爬虫专家4 个月前
深入探讨 Puppeteer 如何使用 X 和 Y 坐标实现鼠标移动
爬虫代理·puppeteer·鼠标·代理ip·小红书·16yun·用户行为
亿牛云爬虫专家4 个月前
捕捉页面的关键元素:用CSS选择器与Puppeteer自动抓取
css·爬虫·爬虫代理·puppeteer·代理ip·机票·特价
亿牛云爬虫专家5 个月前
捕获抖音截图:如何用Puppeteer保存页面状态
爬虫·爬虫代理·puppeteer·抖音·亿牛云·代理ip·douyin
亿牛云爬虫专家5 个月前
Puppeteer教程:使用CSS选择器点击和爬取动态数据
javascript·css·爬虫·爬虫代理·puppeteer·代理ip
亿牛云爬虫专家5 个月前
如何在Puppeteer中实现表单自动填写与提交:问卷调查
javascript·爬虫·爬虫代理·puppeteer·问卷调查·代理ip·表单
亿牛云爬虫专家5 个月前
用Puppeteer点击与数据爬取:实现动态网页交互
javascript·爬虫·爬虫代理·puppeteer·数据·代理ip·16yun
黑金IT5 个月前
Puppeteer点击系统:解锁百度流量点击率提升的解决案例
nodejs·puppeteer·百度排名
黑金IT5 个月前
在浏览器中运行 Puppeteer:解锁新能力
nodejs·puppeteer·浏览器自动化