NodeJS中如何并行运行多个相同脚本实例且互不干扰
方案选型结论
你提到的两个方案都不匹配当前场景:
- PM2的核心能力是常驻进程守护、静态多实例负载均衡,不支持按需动态拉起一次性任务进程,硬适配会出现进程残留、资源泄漏、管理逻辑复杂的问题。
- Node.js原生Cluster模块的设计目标是榨取多核CPU性能做服务负载均衡,工作进程都是常驻模型,用来做动态一次性爬虫任务需要自己从零实现进程生命周期管理、异常回收、通信逻辑,性价比极低。
推荐落地方案
采用动态fork独立子进程 + 并发控制的实现方式,完全满足“任务互相隔离、按需启动、不影响已有任务”的核心需求,改造成本极低。
具体实现步骤如下:
- 拆分独立爬虫执行脚本
把后端爬虫逻辑从现有前端静态资源目录里抽离,在项目根目录新建tasks文件夹存放后台任务,爬虫脚本单独放在tasks/scrape-task.js中。脚本只负责接收主进程传入的用户表单参数,启动Puppeteer实例执行爬取逻辑,执行结束(成功/失败/超时)后将结果回传给主进程,随后自动退出。
注意脚本内必须写全资源回收逻辑:无论任务执行结果如何,都要调用browser.close()关闭Chrome实例;启动Puppeteer时可以关闭非必要功能(比如默认开启的图片加载、沙箱、扩展程序),降低单任务内存占用。 - Express接口层动态拉起独立子进程
在表单提交对应的路由处理逻辑中,不要直接在Express服务线程内执行爬虫逻辑,每次收到合法请求就通过Node.js内置的child_process.fork拉起独立的爬虫子进程:
这种模式下每个爬虫任务都运行在独立的操作系统进程中,单个任务崩溃、卡死完全不会影响其他正在运行的任务,也不会阻塞主Express服务的正常请求处理,进程执行完毕后会自动被系统回收,不会残留冗余资源。const { fork } = require('child_process'); const path = require('path'); // 表单提交路由 router.post('/start-scrape', (req, res) => { const userFormParams = req.body; // 拉起独立爬虫进程,进程间完全隔离 const taskProcess = fork(path.resolve(__dirname, '../tasks/scrape-task.js')); // 向子进程传递用户提交的参数 taskProcess.send({ formData: userFormParams }); // 接收子进程回传的爬取结果 taskProcess.on('message', (taskResult) => { res.json({ success: true, data: taskResult }); }); // 监听进程退出事件,处理异常场景 taskProcess.on('exit', (exitCode) => { if (exitCode !== 0 && !res.headersSent) { res.json({ success: false, msg: '爬取任务执行异常' }); } }); }); - 增加并发控制避免服务器过载
Puppeteer启动的Chrome实例单实例内存占用在80M-150M左右,无限制拉起进程会直接导致服务器内存耗尽OOM。你需要引入轻量任务队列(比如p-queue)控制最大并发数,参考服务器配置设置阈值:2核4G服务器建议最大并发设为3-5,超出阈值的请求进入队列等待,有任务执行完毕释放资源后再启动新任务。
额外需要给每个任务设置超时时间(比如单任务最长执行5分钟),超时后直接kill对应进程,避免死锁、页面卡死导致的资源占用。 - 长任务适配(可选)
如果单爬虫任务执行时间较长,不适合让前端持续等待接口响应,可以给每个启动的任务生成唯一ID,将任务状态(排队中/执行中/成功/失败、结果数据)存在内存或Redis中,前端通过轮询或WebSocket拉取任务状态即可。
适配后的目录结构
调整后的结构完全兼容你现有项目,只需要新增任务目录即可:
my-app/ ├─ bin/ │ ├─ www ├─ node_modules/ ├─ public/ │ ├─ images/ │ ├─ javascript/ │ │ ├─ scrapper.js // 保留为前端侧脚本,后端爬虫逻辑迁移到tasks目录 │ │ ├─ script.js │ ├─ stylesheets/ ├─ routes/ │ ├─ index.js ├─ tasks/ // 新增:存放所有后台执行的任务脚本 │ ├─ scrape-task.js ├─ views/ │ ├─ error.pug │ ├─ index.pug │ ├─ layout.pug ├─ app.json ├─ package-lock.json ├─ package.json
如果后续业务量上涨,单台服务器无法承载并发,只需要把任务投递逻辑替换为消息队列(比如BullMQ),将爬虫脚本拆为独立的Worker节点横向扩展即可,核心逻辑不需要大幅改动。
内容的提问来源于stack exchange,提问作者Nogaruki
相关产品推荐
相关产品推荐

