如何在Puppeteer的page.evaluate()中获取fetch()的流式数据?(Node.js)
解决Puppeteer流式传输多请求失效问题
问题根源
你的代码核心问题在于全局复用单个page实例,且首次请求时通过exposeFunction绑定的是第一个请求的res对象。后续请求进来时,不仅复用了已绑定旧res的page,若多个请求并发,exposeFunction还会被覆盖,导致所有请求的流都指向同一个res,或后续请求的res完全未被关联。
解决方案
复用全局browser实例(浏览器启动开销极高,复用可大幅提升性能),但每个请求创建独立的page实例,并为每个page单独绑定当前请求的res处理函数,确保每个请求的流完全独立。
修改后的代码
const puppeteer = require('puppeteer'); const express = require('express'); const app = express(); // 全局复用browser,避免重复启动浏览器 let browser = null; app.post("/test", async(req, res) => { try { // 首次请求启动浏览器 if (!browser) { browser = await puppeteer.launch({ headless: "new" }); } // 每个请求创建独立的page const page = await browser.newPage(); const desiredUrl = "url here"; await page.goto(desiredUrl); // 给当前page绑定当前请求的res处理函数 await page.exposeFunction('writeData', (data) => { console.log("Writing data for request"); res.write(data); }); await page.exposeFunction('endStream', () => { console.log("End stream for request"); res.end(); // 请求结束后关闭当前page,释放资源 page.close().catch(err => console.error("Close page error:", err)); }); await page.evaluate(async ()=>{ const output = await fetch("/endpoint_here", { headers: { /* 你的headers配置 */ }, }); const reader = output.body.getReader(); while (true) { const { done, value } = await reader.read(); if (done) { window.endStream(); return; } const decoder = new TextDecoder(); const decodedData = decoder.decode(value); window.writeData(decodedData); } }); } catch (err) { console.error("Request error:", err); if (!res.headersSent) { res.status(500).send("Server error"); } } }); // 进程退出时关闭browser,避免资源泄漏 process.on('exit', async () => { if (browser) { await browser.close(); } }); app.listen(3000, () => console.log("Server running on port 3000"));
关键说明
- 全局仅复用
browser:浏览器启动耗时久,复用能降低资源消耗;page是轻量实例,每个请求新建不会有明显性能问题。 - 每个
page绑定独立res:exposeFunction是绑定到当前page实例的,每个请求的page对应自己的res,不会出现串流或失效问题。 - 请求结束后关闭
page:避免page实例堆积占用内存,高频请求场景可改用page池优化。 - 错误处理:通过
try/catch捕获异常,确保请求出错时能正确响应客户端,避免请求挂起。
额外优化建议
- 高频请求场景可引入
page池(如generic-pool库),复用page实例减少创建销毁开销,但需注意复用前重置page状态(清除绑定函数、跳转到空白页等)。 - 流式传输时可设置响应头
res.setHeader('Transfer-Encoding', 'chunked'),明确告知客户端采用分块传输。 - 为
reader.read()添加异常捕获,避免上游服务出错导致page挂起。
内容的提问来源于stack exchange,提问作者Stacky
相关产品推荐
相关产品推荐

