Puppeteer - 协议错误 (Page.navigate):目标已关闭
Puppeteer - Protocol error (Page.navigate): Target closed
正如您在下面的示例代码中看到的那样,我将 Puppeteer 与 Node 中的一组工作人员一起使用 运行 给定 URL:[=14 的网站截图的多个请求=]
const cluster = require('cluster');
const express = require('express');
const bodyParser = require('body-parser');
const puppeteer = require('puppeteer');
async function getScreenshot(domain) {
let screenshot;
const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'] });
const page = await browser.newPage();
try {
await page.goto('http://' + domain + '/', { timeout: 60000, waitUntil: 'networkidle2' });
} catch (error) {
try {
await page.goto('http://' + domain + '/', { timeout: 120000, waitUntil: 'networkidle2' });
screenshot = await page.screenshot({ type: 'png', encoding: 'base64' });
} catch (error) {
console.error('Connecting to: ' + domain + ' failed due to: ' + error);
}
await page.close();
await browser.close();
return screenshot;
}
if (cluster.isMaster) {
const numOfWorkers = require('os').cpus().length;
for (let worker = 0; worker < numOfWorkers; worker++) {
cluster.fork();
}
cluster.on('exit', function (worker, code, signal) {
console.debug('Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal);
Cluster.fork();
});
cluster.on('message', function (handler, msg) {
console.debug('Worker: ' + handler.process.pid + ' has finished working on ' + msg.domain + '. Exiting...');
if (Cluster.workers[handler.id]) {
Cluster.workers[handler.id].kill('SIGTERM');
}
});
} else {
const app = express();
app.use(bodyParser.json());
app.listen(80, function() {
console.debug('Worker ' + process.pid + ' is listening to incoming messages');
});
app.post('/screenshot', (req, res) => {
const domain = req.body.domain;
getScreenshot(domain)
.then((screenshot) =>
try {
process.send({ domain: domain });
} catch (error) {
console.error('Error while exiting worker ' + process.pid + ' due to: ' + error);
}
res.status(200).json({ screenshot: screenshot });
})
.catch((error) => {
try {
process.send({ domain: domain });
} catch (error) {
console.error('Error while exiting worker ' + process.pid + ' due to: ' + error);
}
res.status(500).json({ error: error });
});
});
}
一些解释:
- 每次请求到达时,worker 都会处理它并在最后杀死自己
- 每个工作人员创建一个包含单个页面的新浏览器实例,如果页面加载时间超过 60 秒,它将重试重新加载它(在同一页面中,因为可能已经加载了一些资源)超时为120 秒
- 完成后页面和浏览器都将关闭
我的问题是某些 legitimate 域出现我无法解释的错误:
Error: Protocol error (Page.navigate): Target closed.
Error: Protocol error (Runtime.callFunctionOn): Session closed. Most likely the page has been closed.
我在一些 git 问题(我现在找不到)上读到,当页面重定向并在开头添加 'www' 时可能会发生这种情况,但我希望这是错误的...
有什么我想念的吗?
"Target closed" 是什么意思
当您通过 puppeteer.launch
启动浏览器时,它会启动浏览器并连接到它。从那里开始,您在打开的浏览器上执行的任何功能(如 page.goto
)都将通过 Chrome DevTools Protocol 发送到浏览器。在此上下文中,目标表示选项卡。
当您尝试 运行 一个函数,但目标(选项卡)已经关闭时,会抛出 目标已关闭 异常。
类似的错误信息
最近更改了错误消息以提供更有意义的信息。它现在给出以下消息:
Error: Protocol error (Target.activateTarget): Session closed. Most likely the page has been closed.
为什么会这样
发生这种情况的原因有多种。
您使用了一个已经关闭的资源
很可能,您看到此消息是因为您关闭了 tab/browser 并且仍在尝试使用该资源。举个简单的例子:
const browser = await puppeteer.launch();
const page = await browser.newPage();
await browser.close();
await page.goto('http://www.google.com');
在这种情况下,浏览器已关闭,之后调用了 page.goto
,导致出现错误消息。大多数时候,它不会那么明显。可能错误处理程序已经在清理任务期间关闭了页面,而您的脚本仍在抓取。
浏览器崩溃或无法初始化
我也每隔几百个请求就会遇到这种情况。 puppeteer 存储库中也有一个 issue about this。当您使用大量内存或 CPU 电源时,情况似乎就是如此。也许你产生了很多浏览器?在这些情况下,浏览器可能会崩溃或断开连接。
我找不到 "silver bullet" 这个问题的解决方案。但是您可能想查看处理此类错误情况的库 puppeteer-cluster(免责声明:我是作者),让您在错误发生时重试 URL。它还可以管理浏览器实例池并简化您的代码。
检查你的笑话-puppeteer.config.js 文件。
我犯了以下错误
module.exports = {
launch: {
headless: false,
browserContext: "default",
},
};
更正后如下
module.exports = {
launch: {
headless: false
},
browserContext: "default",
};
一切正常!!!
我每次尝试 运行 我的人偶脚本* 时都会遇到同样的问题。 没有为我解决这个问题。
我通过删除并重新安装 puppeteer 包来让它工作:
npm remove puppeteer
npm i puppeteer
*我只在将 headless 选项设置为“false”时遇到过这个问题
在 2021 年,我收到了非常相似的以下错误 Error: Error pdf creationError: Protocol error (Target.setDiscoverTargets): Target closed.
,我通过使用不同的参数解决了这个问题,所以如果您的生产服务器在 puppeteer.launch
中有一个 pipe:true
标志obj 它会产生错误。
还有 --disable-dev-shm-usage
标志可以解决问题
下面的解决方案适用于我:
const browser = await puppeteer.launch({
headless: true,
// pipe: true, <-- delete this property
args: [
'--no-sandbox',
'--disable-dev-shm-usage', // <-- add this one
],
});
对我来说,从 args
中删除 '--single-process'
解决了这个问题。
puppeteerOptions: {
headless: true,
args: [
'--disable-gpu',
'--disable-dev-shm-usage',
'--disable-setuid-sandbox',
'--no-first-run',
'--no-sandbox',
'--no-zygote',
'--deterministic-fetch',
'--disable-features=IsolateOrigins',
'--disable-site-isolation-trials',
// '--single-process',
],
}
我已经在这个线程上结束了几次,典型的罪魁祸首是我忘记了 await
一个返回承诺的 Puppeteer page
调用,导致了竞争条件。
这是一个简单的示例:
const puppeteer = require("puppeteer");
let browser;
(async () => {
browser = await puppeteer.launch({headless: true});
const [page] = await browser.pages();
page.goto("https://www.whosebug.com"); // whoops, forgot await!
})()
.catch(err => console.error(err))
.finally(() => browser?.close())
;
输出为:
C:\Users\foo\Desktop\puppeteer-playground\node_modules\puppeteer\lib\cjs\puppeteer\common\Connection.js:217
this._callbacks.set(id, { resolve, reject, error: new Error(), method });
^
Error: Protocol error (Page.navigate): Target closed.
at C:\Users\foo\Desktop\puppeteer-playground\node_modules\puppeteer\lib\cjs\puppeteer\common\Connection.js:217:63
在这种情况下,这似乎是一个不容错过的错误,但在较大的代码块中并且 promise 是嵌套的或在条件中,很容易被忽略。
您会因为忘记 await
一个 page.click()
或其他 promise 调用而得到类似的错误,例如 Error: Protocol error (Runtime.callFunctionOn): Target closed.
,这可以在问题 [=17] 中看到=]
这是对线程作为错误的规范资源的贡献,可能不是 OP 问题的解决方案,尽管基本的竞争条件似乎是一个可能的原因.
经过几个小时的挫折,我意识到当它进入一个新页面时会发生这种情况,我需要在我做任何事情之前和按下按钮或做任何会导致它的动作之后使用 await page.waitForNavigation()
重定向。
正如您在下面的示例代码中看到的那样,我将 Puppeteer 与 Node 中的一组工作人员一起使用 运行 给定 URL:[=14 的网站截图的多个请求=]
const cluster = require('cluster');
const express = require('express');
const bodyParser = require('body-parser');
const puppeteer = require('puppeteer');
async function getScreenshot(domain) {
let screenshot;
const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'] });
const page = await browser.newPage();
try {
await page.goto('http://' + domain + '/', { timeout: 60000, waitUntil: 'networkidle2' });
} catch (error) {
try {
await page.goto('http://' + domain + '/', { timeout: 120000, waitUntil: 'networkidle2' });
screenshot = await page.screenshot({ type: 'png', encoding: 'base64' });
} catch (error) {
console.error('Connecting to: ' + domain + ' failed due to: ' + error);
}
await page.close();
await browser.close();
return screenshot;
}
if (cluster.isMaster) {
const numOfWorkers = require('os').cpus().length;
for (let worker = 0; worker < numOfWorkers; worker++) {
cluster.fork();
}
cluster.on('exit', function (worker, code, signal) {
console.debug('Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal);
Cluster.fork();
});
cluster.on('message', function (handler, msg) {
console.debug('Worker: ' + handler.process.pid + ' has finished working on ' + msg.domain + '. Exiting...');
if (Cluster.workers[handler.id]) {
Cluster.workers[handler.id].kill('SIGTERM');
}
});
} else {
const app = express();
app.use(bodyParser.json());
app.listen(80, function() {
console.debug('Worker ' + process.pid + ' is listening to incoming messages');
});
app.post('/screenshot', (req, res) => {
const domain = req.body.domain;
getScreenshot(domain)
.then((screenshot) =>
try {
process.send({ domain: domain });
} catch (error) {
console.error('Error while exiting worker ' + process.pid + ' due to: ' + error);
}
res.status(200).json({ screenshot: screenshot });
})
.catch((error) => {
try {
process.send({ domain: domain });
} catch (error) {
console.error('Error while exiting worker ' + process.pid + ' due to: ' + error);
}
res.status(500).json({ error: error });
});
});
}
一些解释:
- 每次请求到达时,worker 都会处理它并在最后杀死自己
- 每个工作人员创建一个包含单个页面的新浏览器实例,如果页面加载时间超过 60 秒,它将重试重新加载它(在同一页面中,因为可能已经加载了一些资源)超时为120 秒
- 完成后页面和浏览器都将关闭
我的问题是某些 legitimate 域出现我无法解释的错误:
Error: Protocol error (Page.navigate): Target closed.
Error: Protocol error (Runtime.callFunctionOn): Session closed. Most likely the page has been closed.
我在一些 git 问题(我现在找不到)上读到,当页面重定向并在开头添加 'www' 时可能会发生这种情况,但我希望这是错误的... 有什么我想念的吗?
"Target closed" 是什么意思
当您通过 puppeteer.launch
启动浏览器时,它会启动浏览器并连接到它。从那里开始,您在打开的浏览器上执行的任何功能(如 page.goto
)都将通过 Chrome DevTools Protocol 发送到浏览器。在此上下文中,目标表示选项卡。
当您尝试 运行 一个函数,但目标(选项卡)已经关闭时,会抛出 目标已关闭 异常。
类似的错误信息
最近更改了错误消息以提供更有意义的信息。它现在给出以下消息:
Error: Protocol error (Target.activateTarget): Session closed. Most likely the page has been closed.
为什么会这样
发生这种情况的原因有多种。
您使用了一个已经关闭的资源
很可能,您看到此消息是因为您关闭了 tab/browser 并且仍在尝试使用该资源。举个简单的例子:
const browser = await puppeteer.launch(); const page = await browser.newPage(); await browser.close(); await page.goto('http://www.google.com');
在这种情况下,浏览器已关闭,之后调用了
page.goto
,导致出现错误消息。大多数时候,它不会那么明显。可能错误处理程序已经在清理任务期间关闭了页面,而您的脚本仍在抓取。浏览器崩溃或无法初始化
我也每隔几百个请求就会遇到这种情况。 puppeteer 存储库中也有一个 issue about this。当您使用大量内存或 CPU 电源时,情况似乎就是如此。也许你产生了很多浏览器?在这些情况下,浏览器可能会崩溃或断开连接。
我找不到 "silver bullet" 这个问题的解决方案。但是您可能想查看处理此类错误情况的库 puppeteer-cluster(免责声明:我是作者),让您在错误发生时重试 URL。它还可以管理浏览器实例池并简化您的代码。
检查你的笑话-puppeteer.config.js 文件。 我犯了以下错误
module.exports = {
launch: {
headless: false,
browserContext: "default",
},
};
更正后如下
module.exports = {
launch: {
headless: false
},
browserContext: "default",
};
一切正常!!!
我每次尝试 运行 我的人偶脚本* 时都会遇到同样的问题。
我通过删除并重新安装 puppeteer 包来让它工作:
npm remove puppeteer
npm i puppeteer
*我只在将 headless 选项设置为“false”时遇到过这个问题
在 2021 年,我收到了非常相似的以下错误 Error: Error pdf creationError: Protocol error (Target.setDiscoverTargets): Target closed.
,我通过使用不同的参数解决了这个问题,所以如果您的生产服务器在 puppeteer.launch
中有一个 pipe:true
标志obj 它会产生错误。
还有 --disable-dev-shm-usage
标志可以解决问题
下面的解决方案适用于我:
const browser = await puppeteer.launch({
headless: true,
// pipe: true, <-- delete this property
args: [
'--no-sandbox',
'--disable-dev-shm-usage', // <-- add this one
],
});
对我来说,从 args
中删除 '--single-process'
解决了这个问题。
puppeteerOptions: {
headless: true,
args: [
'--disable-gpu',
'--disable-dev-shm-usage',
'--disable-setuid-sandbox',
'--no-first-run',
'--no-sandbox',
'--no-zygote',
'--deterministic-fetch',
'--disable-features=IsolateOrigins',
'--disable-site-isolation-trials',
// '--single-process',
],
}
我已经在这个线程上结束了几次,典型的罪魁祸首是我忘记了 await
一个返回承诺的 Puppeteer page
调用,导致了竞争条件。
这是一个简单的示例:
const puppeteer = require("puppeteer");
let browser;
(async () => {
browser = await puppeteer.launch({headless: true});
const [page] = await browser.pages();
page.goto("https://www.whosebug.com"); // whoops, forgot await!
})()
.catch(err => console.error(err))
.finally(() => browser?.close())
;
输出为:
C:\Users\foo\Desktop\puppeteer-playground\node_modules\puppeteer\lib\cjs\puppeteer\common\Connection.js:217
this._callbacks.set(id, { resolve, reject, error: new Error(), method });
^
Error: Protocol error (Page.navigate): Target closed.
at C:\Users\foo\Desktop\puppeteer-playground\node_modules\puppeteer\lib\cjs\puppeteer\common\Connection.js:217:63
在这种情况下,这似乎是一个不容错过的错误,但在较大的代码块中并且 promise 是嵌套的或在条件中,很容易被忽略。
您会因为忘记 await
一个 page.click()
或其他 promise 调用而得到类似的错误,例如 Error: Protocol error (Runtime.callFunctionOn): Target closed.
,这可以在问题 [=17] 中看到=]
这是对线程作为错误的规范资源的贡献,可能不是 OP 问题的解决方案,尽管基本的竞争条件似乎是一个可能的原因.
经过几个小时的挫折,我意识到当它进入一个新页面时会发生这种情况,我需要在我做任何事情之前和按下按钮或做任何会导致它的动作之后使用 await page.waitForNavigation()
重定向。