查看: 111|回复: 1

Node.js调用Python脚本:spawn子进程与FastAPI HTTP方案对比实践

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
在 Node.js 项目中经常需要调用 Python 脚本处理数据或执行算法。本文基于实际项目经验,对比两种主流方案:通过 child_process.spawn 启动子进程,以及基于 FastAPI 搭建 HTTP 服务。包括环境配置、代码实现、锁机制、超时处理与性能优化细节,帮助开发者在稳定性和效率之间做出正确选择。

一、项目环境与虚拟环境管理

推荐在项目根目录下创建 python/ 文件夹,内建 scripts/ 子目录存放 Python 脚本。进入 python/ 目录,使用 Python 内置 venv 创建隔离环境:
  1. python -m venv venv
复制代码

Windows PowerShell 中激活:
  1. venv\Scripts\Activate.ps1
复制代码

安装所需依赖:
  1. pip install pandas requests fastapi uvicorn[standard]
复制代码

生成依赖清单(提交到 Git):
  1. pip freeze > requirements.txt
复制代码

确保 .gitignore 忽略虚拟环境与缓存文件:
  1. python/venv/
  2. __pycache__/
  3. *.pyc
复制代码

服务器部署时重新创建环境:
  1. python -m venv venv
  2. pip install -r requirements.txt
复制代码

二、方案一:child_process.spawn 子进程(仅适用于低频调用)

原理:每个请求 spawn 一个 Python 进程执行脚本,通过 stdout/stderr 获取输出。适合低频、一次性任务,但不推荐高频并发使用。

2.1 关键实现

在 Node 端,需要构建 Python 解释器的绝对路径和脚本路径。使用 path.join 拼接,确保跨平台兼容。
  1. const { spawn } = require('child_process');
  2. const path = require('path');
  3. const rootPath = path.resolve(__dirname, '..');
  4. const pythonPath = path.join(rootPath, 'python', 'venv', 'Scripts', 'python.exe');
  5. const scriptPath = path.join(rootPath, 'python', 'scripts', 'test.py');
  6. let isRunning = false;
  7. exports.get_info = (req, res) => {
  8.   if (isRunning) {
  9.     return res.send({ status: 429, message: '任务执行中,请稍后再试' });
  10.   }
  11.   isRunning = true;
  12.   const py = spawn(pythonPath, [scriptPath]);
  13.   let error = '';
  14.   py.stderr.on('data', (data) => { error += data.toString(); });
  15.   py.on('error', (err) => {
  16.     isRunning = false;
  17.     return res.send({ status: 500, message: 'Python启动失败', error: err.message });
  18.   });
  19.   const timeout = setTimeout(() => py.kill('SIGTERM'), 300000); // 5分钟超时
  20.   py.on('close', (code) => {
  21.     clearTimeout(timeout);
  22.     isRunning = false;
  23.     if (code === 0) return res.send({ status: 200, message: '执行成功' });
  24.     return res.send({ status: 500, message: '执行失败', error: error || '未知错误' });
  25.   });
  26. };
复制代码

2.2 需要特别注意的陷阱

- 全局锁与并发限制:每个 spawn 都会创建新进程,并发请求会导致内存飙升。通过 isRunning 锁确保同一时刻只执行一个 Python 任务,后面的请求直接返回 429。
- Python 脚本内线程数控制:在脚本最顶部限制 OpenBLAS / OMP / MKL 线程数,防止多线程引发内存溢出:
  1. import os
  2. os.environ['OPENBLAS_NUM_THREADS'] = '1'
  3. os.environ['OMP_NUM_THREADS'] = '1'
  4. os.environ['MKL_NUM_THREADS'] = '1'
复制代码

- 避免 stdout 污染:Python 脚本中不要随意 print,否则输出会被 Node 端捕获并可能污染解析。建议只在必要时报 JSON 格式数据,由 Node 端正则解析。
- 路径问题:若脚本内有文件读写操作,必须使用绝对路径,否则工作目录不固定可能导致文件找不到。
- 进程退出与内存回收:超时杀死进程 (SIGTERM) 后,系统会自动回收内存,但若进程僵死则无法回收,后续请求会失败。

三、方案二:FastAPI HTTP 服务(推荐)

摆脱 spawn 的各种陷阱:buffer 溢出、JSON 污染、进程失控。让 Python 作为一个长期运行的 HTTP 服务,Node 通过 axios 发起请求。

3.1 启动 FastAPI 服务

在虚拟环境中安装依赖后,创建 api_server.py 作为入口:
  1. # api_server.py
  2. import os
  3. from fastapi import FastAPI
  4. import sys
  5. BASE_DIR = os.path.dirname(os.path.abspath(__file__))
  6. sys.path.append(BASE_DIR)  # 使 scripts 可导入
  7. from scripts.test import get_test_info
  8. app = FastAPI()
  9. @app.get('/aaa/bbb')
  10. async def ccc():
  11.     result = await get_test_info()
  12.     return result
复制代码

注意:FastAPI 已在事件循环中,脚本文件内不可使用 asyncio.run(),而是把原来的 __main__ 逻辑改写为 async 函数并 return 可序列化的字典。

启动命令(在 python/ 目录下):
  1. uvicorn api_server:app --host 0.0.0.0 --port 8000 --reload
复制代码

--reload 用于开发阶段自动重启。生产部署时可去掉。

3.2 Node 端调用

使用 axios 替代 spawn:
  1. const axios = require('axios');
  2. let isRunning = false;
  3. exports.get_info = async (req, res) => {
  4.   if (isRunning) {
  5.     return res.send({ status: 429, message: '任务执行中,请稍后再试' });
  6.   }
  7.   isRunning = true;
  8.   try {
  9.     const response = await axios.get('http://127.0.0.1:8000/aaa/bbb', { timeout: 300000 });
  10.     isRunning = false;
  11.     return res.send({ status: 200, message: '执行成功', data: response.data });
  12.   } catch (error) {
  13.     isRunning = false;
  14.     return res.send({ status: 500, message: 'FastAPI调用失败', error: error.message });
  15.   }
  16. };
复制代码

3.3 优势分析

- 无 spawn 进程开销:Python 进程常驻,请求只需一次 HTTP 往返。
- 天然避免并发爆炸:FastAPI 内部处理并发,可根据需要调整 worker 数。
- 易于监控:可加健康检查端点,Prometheus 监控等。
- 错误处理清晰:HTTP 状态码与 JSON 响应,Node 端直接捕获异常。

四、方案选型建议

- 低频任务(每天数十次):可使用 spawn 单进程加锁。注意务必加上超时杀进程和线程限制,预防内存泄漏。
- 高频调用或对稳定性要求高:优先选择 FastAPI HTTP 方案。多部署一个 Python 服务,但换来稳定性和可维护性。
- 特殊场景:若 Python 脚本需要实时交互(如逐行输入),spawn 的 stdin/stdout 模式仍可用,但需设计好通信协议。

无论选择哪种方式,都需处理好路径、锁、超时和错误回传,才能保证 Node.js + Python 混合架构的健壮运行。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Node.js调用Python脚本:spawn子进程与FastAPI HTTP方案对比实践

非常详细的对比分享!两种方案的优缺点和注意事项都讲得很清楚,特别是spawn方案里的全局锁、线程数限制和stdout污染这些容易踩坑的点,非常有实际参考价值。 我平时在项目里也经常遇到Node.js调Python的需求,简单场景用过spawn,但并发一高问题就多,后来换成了HTTP服务。FastAPI那一段的配置和启动命令很实用,尤其是提醒脚本内不能用asyncio.run、要改成async函数这个细节,新手特别容易忽略。 想请教两个问题: 1. 在FastAPI方案中,如果Python脚本执行时间很长,比如超过请求超时时间,你是如何处理Node端和Python端之间的超时协调的? 2. 如果Python服务需要同时处理多个不同请求,有没有考虑过请求队列或限制并发? 谢谢分享,期待更多实战经验!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-6-13 13:00 , Processed in 0.044825 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部