使用Puppeteer exposeFunction后如何正确关闭浏览器?
解决Puppeteer中关闭浏览器时出现Target closed错误的问题
我明白你遇到的麻烦了——当你用exposeFunction把页面数据传回Node.js并写入文件后,直接调用browser.close()会抛出Protocol error (Runtime.evaluate): Target closed的错误,必须依赖生命周期事件或者超时才能正常关闭浏览器。这其实是因为浏览器和Node.js之间的通信时机没把握好,我来帮你理清原因并给出解决方案。
错误原因
当你在reader函数里写完文件就立刻调用browser.close()时,浏览器端其实还在等待这个函数的执行结果返回。exposeFunction注入的函数是在Node.js环境运行的,浏览器会一直等待这个函数的Promise完成,此时强行关闭浏览器会直接切断通信通道,导致报错。
正确的解决方案
我们需要把事件监听和业务逻辑分开,确保在浏览器完成所有必要通信后再关闭。这里有个更规范的实现方式:
const fs = require('fs'); const path = require('path'); const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch({}); const [page] = await browser.pages(); const session = await page.createCDPSession(); // 提前监听页面生命周期事件,选一个合适的时机关闭浏览器 session.on('Page.lifecycleEvent', (event) => { console.log(JSON.stringify(event)); // 推荐用loadEventFired,它表示页面所有资源都加载完成,更稳妥 if (event.name === 'loadEventFired') { // 用catch捕获可能的关闭错误,避免未处理的Promise异常 browser.close().catch(err => console.error('关闭浏览器失败:', err)); } }); await page.goto('file:///path/to/your/target/file'); // 注入处理页面数据的函数 await page.exposeFunction('reader', (html) => { try { const outputPath = path.join(__dirname, pdf.replace('.pdf', '.htm')); // 记得提前定义pdf变量 fs.writeFileSync(outputPath, html); console.log('HTML文件已成功保存'); } catch (err) { console.error('写入文件时出错:', err); } }); // 触发页面调用reader函数(如果页面本身会自动调用,这一步可以省略) await page.evaluate(() => { const pageHtml = document.documentElement.outerHTML; return window.reader(pageHtml); }); })();
关键细节说明
提前监听生命周期事件:把CDP会话的事件监听放在页面加载前,确保不会错过关键事件。你可以根据需求选择不同的事件:
domContentEventFired:DOM结构加载完成时触发,适合不需要等待资源加载的场景networkAlmostIdle:网络请求基本停止时触发firstMeaningfulPaint:页面首次完成有意义绘制时触发(就是你之前用的)loadEventFired:页面所有资源(图片、脚本等)都加载完成时触发,最稳妥
避免在exposeFunction回调里直接关闭浏览器:
reader函数只负责处理数据写入,关闭浏览器的逻辑交给生命周期事件,确保浏览器已经完成和Node.js的通信。
这样修改后,你就不需要依赖超时这类技巧,也能正常关闭浏览器,不会再出现Target closed的错误了。
内容的提问来源于stack exchange,提问作者Jan
相关产品推荐
相关产品推荐

