如何保持Windows Server会话活跃以全屏运行Puppeteer?
解决Windows Server断开RDS后Puppeteer/Chrome冻结的问题
问题根源
断开RDS连接后,Windows Server默认会冻结或注销用户会话,Chrome非无头模式依赖用户会话的桌面环境运行,一旦会话被冻结,Chrome的GUI进程会停止响应,导致Puppeteer的CDP连接中断,触发Execution context was destroyed错误。
解决方案
1. 配置Windows Server保持RDS会话不冻结
修改系统策略和会话设置,确保断开RDS后用户会话仍保持活跃:
- 打开本地组策略编辑器(
gpedit.msc):- 导航到
计算机配置 > 管理模板 > Windows组件 > 远程桌面服务 > 远程桌面会话主机 > 会话时间限制 - 设置
设置断开连接的会话的时间限制为已禁用 - 设置
设置活动但空闲的远程桌面服务会话的时间限制为已禁用
- 导航到
- 打开远程桌面会话主机配置(
tsconfig.msc):- 右键选中目标会话,点击属性
- 在会话选项卡中,设置
结束已断开的会话为从不,空闲会话限制为从不
- 检查用户账户设置:
- 打开计算机管理,导航到
本地用户和组 > 用户 - 右键目标用户,点击属性,在会话选项卡中确保
结束已断开的会话为从不
- 打开计算机管理,导航到
2. 以Windows服务运行Chrome和Node.js应用
将Chrome和你的Express应用注册为系统服务,脱离用户会话运行(系统会话0不受RDS断开影响):
- 使用NSSM工具将Node.js应用包装为服务:
- 运行
nssm install [服务名],在配置窗口中指定Node.exe路径、脚本路径和项目工作目录
- 运行
- 配置Chrome在服务中启动:
- 启动Chrome时添加参数:
chrome.exe --no-sandbox --disable-gpu --remote-debugging-port=9222 - 同样用NSSM将Chrome注册为服务,设置启动类型为自动
- 注意:部分Windows版本需给服务开启
允许服务与桌面交互权限,或搭配虚拟显示驱动使用
- 启动Chrome时添加参数:
3. 安装虚拟显示驱动
非无头Chrome需要显示输出,安装虚拟显示驱动模拟桌面环境,即使没有真实显示器或用户会话,Chrome也能正常运行:
- 安装虚拟显示驱动(如Virtual Display Adapter),系统会生成虚拟显示器
- 启动Chrome时可指定分辨率参数:
--window-size=1920,1080
脚本优化建议
全局复用browser和page实例会导致会话失效后无法恢复,建议每次请求创建新实例并及时清理:
const express = require('express'); const puppeteer = require('puppeteer'); const app = express(); app.get('/scrape', async (req, res) => { let result = {}; const { url } = req.query; if (!url) { return res.status(400).send('请提供要爬取的URL'); } console.log(`Scraping URL: ${url}`); let browser = null; let page = null; // 连接浏览器 console.log(`Launch browser..`); try { browser = await puppeteer.connect({ browserURL: 'http://localhost:9222', defaultViewport: null, }); } catch (error) { console.error('An error occurred:', error); result.error=`无法连接到浏览器: ${error}`; return res.send(result); } // 创建新页面 console.log(`Get page..`); try { page = await browser.newPage(); } catch (error) { console.error('An error occurred:', error); await browser?.close(); result.error=`无法创建页面: ${error}`; return res.send(result); } // 导航到目标URL console.log(`Navigate..`); try { const response = await page.goto(url, { waitUntil: 'domcontentloaded' }); const statusCode = response.status(); result.statusCode = statusCode; if (statusCode !== 200) { result.error = "无效状态码"; return res.send(result); } } catch (error) { console.error('An error occurred:', error); result.error=`无法导航到URL: ${error}`; return res.send(result); } // 后续爬取逻辑(获取标题、检查侧边栏、点击电话链接等)... // 清理资源 try { await page?.close(); await browser?.disconnect(); } catch (cleanupErr) { console.error('清理资源时出错:', cleanupErr); } res.send(result); }); app.listen(3000, () => { console.log('Server running on port 3000'); }); function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
内容的提问来源于stack exchange,提问作者Alexandru Trandafir Catalin
相关产品推荐
相关产品推荐

