Puppeteer + Nodejs 通用全屏网页截图方案(四)项目优化及部署相关

简介: 接下来介绍项目优化及部署相关

JS实现异步任务队列

由于截图服务每次执行任务都需要占用不少时间和性能,且服务器资源有限,如果截图服务并发请求数量过高,必然会引起问题,此时就需要一个异步任务队列来对并发的浏览器数量进行控制。

// 在前面创建的配置文件 config.ts 中加入配置
exports.maxNum = 1 // 截图队列并发数(阈值)

队列方法参考:

// node-queue.ts
interface Queue {
  Fn: Function
  sign?: string | number
}

const { maxNum } = require('../configs.ts')
const queueList: any = [] // 任务队列
let curNum = 0 // 当前执行的任务数
// 队列处理器,将超过允许并行数的任务抛进队列中等待执行
function queueRun(business: Function, ...arg: any) {
  return new Promise(async (resolve) => {
    const Fn = async () => resolve(await business(...arg)) // 核心是将业务函数封装起来
    const sign = { ...arg }[2] // 标记任务的参数,后面会用到
    if (curNum >= maxNum) {
      queueList.push({ sign, Fn })
    } else {
      await run(Fn)
    }
  })
}
// 任务执行器,将队列中等待的任务取出执行
function run(Fn: Function) {
  curNum++
  Fn().then((res: any) => {
    curNum--
    if (queueList.length > 0) { // 如果存在任务,继续取出执行
      const Task: Queue = queueList.shift()
      run(Task.Fn)
    }
    return res
  })
}

module.exports = { queueRun, queueList }

请求服务:

const { queueRun, queueList } = require('../utils/node-queue.ts')
const screenshotFn = require(xxxxxx) // 这里引入你的截图方法
// ...... 这里使用的是一个express框架的服务,只贴出部分关键代码 .....
async printscreen(req: any, res: any) {
if (queueList.length > 100) { // 限制在达到某个最大队列数量时直接暂停服务
    res.json({ code: 200, msg: '任务繁忙,请稍候再试!' })
    return
}
// 进入队列处理,最终执行的是:screenshotFn(url, { width, height ...等参数.. })
    queueRun(screenshotFn, url, { width, height ...等参数.. })
        .then(() => {
          res.setHeader('Content-Type', 'image/jpg') // 请求结果将直接返回图片
          type === 'file' ? res.sendFile(path) : res.sendFile(thumbPath)
        })
        .catch((e: any) => {
          res.json({ code: 500, e })
        })
}

express超时任务销毁

有了前面的队列处理,在任务跑满的情况下基本可以将性能稳定下来,但是服务的健壮性还有所欠缺。假设现在有100个任务同时并发,每个任务的平均处理时间约为10秒,并发任务阈值为1,那么处理到第30个任务的时候已经过了约5分钟的时间了,浏览器默认超时时间大概也就4到5分钟(大部分的项目可能会手动设置一个30~60秒的请求超时)在http请求中如果浏览器超时了,服务端是不知道的,而我们的任务还在不断执行着,于是后面这几十条任务等于没办法把结果返回给前端了,因为链接早已断开。

此时我们需要约定一个超时时间,这里的超时时间最好是服务端<客户端,这样超时的时候请求还在,可以将错误信息返回给前端。

在服务端还需要做一个超时任务销毁的动作,因为超时的任务实际上已经没有存在队列里的必要了,如果不断叠加请求,队列中的无效任务一直在耗时处理,那么后面的所有任务都将会无法正常执行。

中间件 timeout 代码参考:

// timeout.ts
module.exports = async (req: any, res: any, next: any) => {
  const { queueList } = require('../utils/node-queue.ts')
  const time = 30000 // 设置所有HTTP请求的服务器响应超时时间
  res.setTimeout(time, () => {
    const statusCode = 408
    const index = queueList.findIndex((x: any) => x.sign === req._queueSign)
    if (index !== -1) { // 如果超时任务存在队列中,则移除该任务
      queueList.splice(index, 1)
      if (!res.headersSent) { // 如果链接还存在,则返回错误信息
        res.status(statusCode).json({
          statusCode,
          message: '响应超时,任务已取消,请重试',
        })
      }
    }
  })
  next()
}

这里的 sign 在前面队列方法中也有传入,主要是为了给任务一个标记,在超时处理中才好找出对应的任务,只要是不重复的字符即可,这里简单使用时间戳来标记。

// 请求服务的部分关键代码
async printscreen(req: any, res: any) {
    const sign = new Date().getTime() + '' // 时间戳标记
    req._queueSign = sign // 在此次请求中记录标记值
    queueRun(screenshotFn, url, { width, height ...等参数.. }, sign) // 增加了第三个参数,对应前面的{ ...arg }[2]
        .then(() => {
          if (!res.headersSent) {
              // TODO 正常请求的返回动作
          }
        })
        .catch((e: any) => {
          res.json({ code: 500, e })
        })
}
// ........
app.use(handleTimeout) // 别忘了注册中间件

并发任务阈值设为1手动请求几次看看效果:
image.png
最终效果超时任务不会再执行,接口的返回也正常。

koa的超时处理方式与之类似,注意koa是洋葱模型,需要把异常捕获的中间件放在最前面

下面介绍一下在服务端部署的问题以及本地调试的方法。

本地调试

先说说本地如何调试,通常情况下使用 npm installyarn 就会自动下载好浏览器的依赖了,不需要自己配置,在nodemodules里已经配好,本地调试的时候关闭掉无头浏览器模式,运行puppeteer的时候会看到程序调起一个蓝色logo的谷歌开发版浏览器并开始进行自动操作,代码如下:

const isDev = process.env.NODE_ENV === 'development' // node下判断是生产环境还是dev环境
// 启动浏览器
    const browser = await puppeteer.launch({
      headless: !isDev, // 本地调试时关闭无头
    })

服务器配置

在Linux的环境下,浏览器没有视图界面,此时就需要使用无头的模式才能截图,linux浏览器正常来说npm会帮忙下载并引用好依赖,但是我实际情况下并未成功,索性自己安装浏览器并配置路径,等下会讲到,参考配置如下:

创建一份配置文件例如:config.js

// 服务器上的浏览器路径,按你的实际路径来,默认安装路径都是在 /opt/google 下面
exports.executablePath = '/opt/google/chrome-unstable/chrome'
const isDev = process.env.NODE_ENV === 'development' // 环境判断
const { executablePath } = require('../configs') // 引入上面的配置

// 启动浏览器
    const browser = await puppeteer.launch({
      headless: !isDev, // 生产环境时为true,即为打开无头模式
      executablePath: isDev ? null : executablePath, // 服务器上指定调用linux浏览器
      ignoreHTTPSErrors: true, // 忽略https安全提示阻塞页面
      // 下面一些优化的选项,涉及关闭沙箱模式、禁用gpu、禁用共享内存等,照着配就行
      args: ['–no-first-run', '–single-process', '–disable-gpu', '–no-zygote', '–disable-dev-shm-usage', '--no-sandbox', '--disable-setuid-sandbox'],
      defaultViewport: null,
    })

一些可能用到的linux命令参考:

google-chrome --version # 查看浏览器版本号

apt-get update
apt-get install -y google-chrome-stable // 安装最新稳定版谷歌浏览器

Docker容器

可以通过docker运行一个带linux浏览器的容器,然后暴露一个截图服务以供使用,我使用的基础镜像为:

docker pull howard86/puppeteer_node:12

运行容器参考(其中映射/cache为临时目录,放生成图片用):

docker run -itd -v /data/docker-home:/home -v /data/cache:/cache -p 7001:7001 --name screenshot howard86/puppeteer_node:12

运行后可以手动进入容器中查看谷歌浏览器版本,看需不需要升级,安装pm2作为服务启动工具,服务启动/重部署相关脚本命令参考:

docker exec screenshot /bin/bash -c 'pm2 delete screenshot-service'
docker exec screenshot /bin/bash -c 'cd /home/ && yarn'
docker exec screenshot /bin/bash -c 'pm2 start /home/screenshot-service.js'
docker exec screenshot /bin/bash -c 'pm2 flush'

另一种方式是在本地/服务器先运行镜像,进入容器中配置好pm2,然后把做好的容器导出为新的镜像,例如:new-design/screenshot,命令运行参考:

docker run -itd -u root -v ~/data/tmp/screenshot:/cache -p 9001:9001 --name screenshot2 new-design/screenshot /bin/sh  -c "/usr/local/bin/pm2 start /home/dist/server.js && /usr/local/bin/pm2 flush"

第二种方式比较适合在公司部署服务,把镜像丢给运维一键启动就行,每次重新部署也就是重启整个容器,比较方便。

相关文章
|
3月前
|
数据采集 Web App开发 JavaScript
Puppeteer的高级用法:如何在Node.js中实现复杂的Web Scraping
随着互联网的发展,网页数据抓取已成为数据分析和市场调研的关键手段。Puppeteer是一款由Google开发的无头浏览器工具,可在Node.js环境中模拟用户行为,高效抓取网页数据。本文将介绍如何利用Puppeteer的高级功能,通过设置代理IP、User-Agent和Cookies等技术,实现复杂的Web Scraping任务,并提供示例代码,展示如何使用亿牛云的爬虫代理来提高爬虫的成功率。通过合理配置这些参数,开发者可以有效规避目标网站的反爬机制,提升数据抓取效率。
302 4
Puppeteer的高级用法:如何在Node.js中实现复杂的Web Scraping
|
26天前
|
存储 缓存 JavaScript
如何优化Node.js应用的内存使用以提高性能?
通过以上多种方法的综合运用,可以有效地优化 Node.js 应用的内存使用,提高性能,提升用户体验。同时,不断关注内存管理的最新技术和最佳实践,持续改进应用的性能表现。
116 62
|
4月前
|
消息中间件 JavaScript 中间件
函数计算产品使用问题之WebIDE编写的Node.js代码是否会自动进行打包部署
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
4月前
|
资源调度 JavaScript Linux
【Azure 应用服务】本地Node.js部署上云(Azure App Service for Linux)遇到的三个问题解决之道
【Azure 应用服务】本地Node.js部署上云(Azure App Service for Linux)遇到的三个问题解决之道
|
22天前
|
存储 缓存 监控
如何使用内存监控工具来优化 Node.js 应用的性能
需要注意的是,不同的内存监控工具可能具有不同的功能和特点,在使用时需要根据具体工具的要求和操作指南进行正确使用和分析。
65 31
|
1月前
|
JavaScript C++ 容器
【Azure Bot Service】部署NodeJS ChatBot代码到App Service中无法自动启动
2024-11-12T12:22:40.366223350Z Error: Cannot find module 'dotenv' 2024-11-12T12:40:12.538120729Z Error: Cannot find module 'restify' 2024-11-12T12:48:13.348529900Z Error: Cannot find module 'lodash'
42 11
|
1月前
|
JSON 监控 JavaScript
Node.js-API 限流与日志优化
Node.js-API 限流与日志优化
|
1月前
|
数据采集 存储 JavaScript
如何使用Puppeteer和Node.js爬取大学招生数据:入门指南
本文介绍了如何使用Puppeteer和Node.js爬取大学招生数据,并通过代理IP提升爬取的稳定性和效率。Puppeteer作为一个强大的Node.js库,能够模拟真实浏览器访问,支持JavaScript渲染,适合复杂的爬取任务。文章详细讲解了安装Puppeteer、配置代理IP、实现爬虫代码的步骤,并提供了代码示例。此外,还给出了注意事项和优化建议,帮助读者高效地抓取和分析招生数据。
如何使用Puppeteer和Node.js爬取大学招生数据:入门指南
|
2月前
|
SQL JavaScript 关系型数据库
node博客小项目:接口开发、连接mysql数据库
【10月更文挑战第14天】node博客小项目:接口开发、连接mysql数据库
|
3月前
|
数据采集 存储 JavaScript
Puppeteer的高级用法:如何在Node.js中实现复杂的Web Scraping
在现代Web开发中,数据采集尤为重要,尤其在财经领域。本文以“东财股吧”为例,介绍如何使用Puppeteer结合代理IP技术进行高效的数据抓取。Puppeteer是一个强大的Node.js库,支持无头浏览器操作,适用于复杂的数据采集任务。通过设置代理IP、User-Agent及Cookies,可显著提升抓取成功率与效率,并以示例代码展示具体实现过程,为数据分析提供有力支持。
130 2
Puppeteer的高级用法:如何在Node.js中实现复杂的Web Scraping
下一篇
DataWorks