使用 puppeteer,我编写了一个将从命令行执行的代码,我想打开浏览器,导航到网页,保持浏览器打开并保留对命令行的控制。
// index.js
(async() => {
const browser = await puppeteer.launch({
headless: false,
});
const page = await browser.newPage();
await page.goto("https://stackoverflow.com");
})();
但是当浏览器打开时,命令行永远不会重新获得控制权(必须调用
browser.close()
,但这不是我想要的)。我怎样才能使代码不阻塞命令行? (我不想从命令行执行代码后台(将 & 附加到执行命令))
听起来您想使用 Puppeteer 启动浏览器,然后在不关闭浏览器的情况下断开连接,然后能够使用另一个脚本重新连接到同一浏览器,或者下次运行同一脚本时。
问题在于,当 Node 进程使用
puppeteer.launch()
生成浏览器进程时,子浏览器进程并未分离,因此杀死父进程也会杀死子进程。根据 this current open issues,目前没有分离浏览器子进程的选项。
在支持之前,这里有一个在 Ubuntu 22.04 上测试的有点 hacky 的解决方法,使用 Chromium 版本 125.0.6422.141(官方版本)snap(64 位)、Node 22.11.1 和 Puppeteer ^22.10.0,使用 Node 的
spawn
来启动浏览器并分离子项。
const {setTimeout} = require("node:timers/promises");
const {spawn} = require("node:child_process");
const puppeteer = require("puppeteer"); // ^22.10.0
const reconnectOrLaunch = async (
browserURL = "http://localhost:9222"
) => {
try {
return await puppeteer.connect({browserURL});
} catch (err) {
const childProcess = spawn(
"chromium",
[`--remote-debugging-port=${browserURL.split(":").pop()}`],
{
detached: true,
stdio: "ignore",
}
);
childProcess.unref();
// Hack: poll until browser connection succeeds
for (let tries = 1_000; tries > 0; tries--) {
await setTimeout(100);
try {
return await puppeteer.connect({browserURL});
} catch (err) {}
}
throw Error("Unable to connect to browser");
}
};
let browser;
(async () => {
browser = await reconnectOrLaunch();
const [page] = await browser.pages();
await page.goto("https://www.example.com", {
waitUntil: "domcontentloaded",
});
console.log(await page.$eval("h1", el => el.textContent));
})()
.catch(err => console.error(err))
.finally(() => browser?.disconnect());
由于浏览器尚未立即准备就绪,因此我将进行轮询,直到其准备就绪。当然还有改进的空间,但这应该可以帮助您开始。
另请参阅在 Puppeteer 中连接浏览器,了解有关连接和断开远程调试端口的一些背景知识。