Puppeteer Execution 上下文被破坏,很可能是因为导航
Puppeteer Execution context was destroyed, most likely because of a navigation
当我转到另一个页面获取数据时,我在 for 循环中的 puppeteer 中遇到了这个问题,然后当我返回时,它出现了这个错误行:
Error "We have an error Error: the execution context was destroyed, probably because of a navigation."
这是一个目录页面,每页包含 15 个公司,然后我想访问每个公司以获取信息。
try {
const browser = await pupputer.launch({
headless: false,
devtools: true,
defaultViewport: {
width: 1100,
height: 1000
}
});
const page = await browser.newPage();
await page.goto('MyLink');
await page.waitForSelector('.list-firms');
for (var i = 1; i < 10; i++) {
const listeCompanies = await page.$$('.list-firms > div.firm');
for (const companie of listeCompanies) {
const name = await companie.$eval('.listing-body > h3 > a', name => name.innerText);
const link = await companie.$eval('.listing-body > h3 > a', link => link.href);
await Promise.all([
page.waitForNavigation(),
page.goto(link),
page.waitForSelector('.firm-panel'),
]);
const info = await page.$eval('#info', e => e.innerText);
const data = [{
name: name,
information: info,
}];
await page.goBack();
}
await Promise.all([
page.waitForNavigation(),
page.click('span.page > a[rel="next"]')
]);
}
} catch (e) {
console.log('We have error', e);
}
我只拿到了第一家公司的数据
问题
该错误意味着您正在访问的数据由于导航而变为 obsolete/invalid。在您的脚本中,错误引用了变量 listeCompanies
:
const listeCompanies = await page.$$('.list-firms > div.firm');
您首先在循环中使用此变量,然后通过 page.goto
导航,然后您的循环尝试从变量 listeCompanies
中获取下一项。但是在导航发生后,该变量中的元素句柄不再存在,因此会引发错误。这也是第一次迭代有效的原因。
解决方案
有多种方法可以解决这个问题。
- 立即从您的页面中提取数据(在使用循环之前)
- 使用第二个页面来执行 "loop navigation" 这样您的主页就不需要导航了
- "Refresh" 您的变量,方法是在调用
page.goBack
后重新执行选择器
选项 1:在进入循环之前提取数据
这是最简洁的方法。您一次提取第一页中的信息,然后迭代提取的数据。 nameLinkList
将是一个包含 name
和 link
值的数组(例如 [{name: '..', link: '..'}, {name: '..', link: '..'}]
)。也不需要在循环结束时调用 page.goBack
,因为数据已经被提取。
const nameLinkList = await page.$$eval(
'.list-firms > div.firm',
(firms => firms.map(firm => {
const a = firm.querySelector('.listing-body > h3 > a');
return {
name: a.innerText,
link: a.href
};
}))
);
for (const {name, link} of arr) {
await Promise.all([
page.waitForNavigation(),
page.goto(link),
page.waitForSelector('.firm-panel'),
]);
const info = await page.$eval('#info', e => e.innerText);
const data = [{
name: name,
information: info,
}];
}
选项 2:使用第二页
在这种情况下,您的浏览器将打开两个页面。第一个只用于读取数据,第二个用于导航。
const page2 = await browser.newPage();
for (const companie of listeCompanies ){
const name = await companie.$eval('.listing-body > h3 > a', name => name.innerText);
const link = await companie.$eval('.listing-body > h3 > a', link => link.href);
await Promise.all([
page2.goto(link),
page2.waitForSelector('.firm-panel'),
]);
const info = await page2.$eval('#info', e => e.innerText);
// ...
}
选项 3:"Refresh" 个选择器
在这里,您只需在返回 "main page" 后重新执行您的选择器。请注意,在我们替换数组时,for..of
必须更改为迭代器循环。
let listeCompanies = await page.$$('.list-firms > div.firm');
for (let i = 0; i < listeCompanies.length; i++){
// ...
await page.goBack();
listeCompanies = await page.$$('.list-firms > div.firm');
}
我建议使用选项 1,因为这也减少了必要的导航请求的数量,因此会加快您的脚本。
当我转到另一个页面获取数据时,我在 for 循环中的 puppeteer 中遇到了这个问题,然后当我返回时,它出现了这个错误行:
Error "We have an error Error: the execution context was destroyed, probably because of a navigation."
这是一个目录页面,每页包含 15 个公司,然后我想访问每个公司以获取信息。
try {
const browser = await pupputer.launch({
headless: false,
devtools: true,
defaultViewport: {
width: 1100,
height: 1000
}
});
const page = await browser.newPage();
await page.goto('MyLink');
await page.waitForSelector('.list-firms');
for (var i = 1; i < 10; i++) {
const listeCompanies = await page.$$('.list-firms > div.firm');
for (const companie of listeCompanies) {
const name = await companie.$eval('.listing-body > h3 > a', name => name.innerText);
const link = await companie.$eval('.listing-body > h3 > a', link => link.href);
await Promise.all([
page.waitForNavigation(),
page.goto(link),
page.waitForSelector('.firm-panel'),
]);
const info = await page.$eval('#info', e => e.innerText);
const data = [{
name: name,
information: info,
}];
await page.goBack();
}
await Promise.all([
page.waitForNavigation(),
page.click('span.page > a[rel="next"]')
]);
}
} catch (e) {
console.log('We have error', e);
}
我只拿到了第一家公司的数据
问题
该错误意味着您正在访问的数据由于导航而变为 obsolete/invalid。在您的脚本中,错误引用了变量 listeCompanies
:
const listeCompanies = await page.$$('.list-firms > div.firm');
您首先在循环中使用此变量,然后通过 page.goto
导航,然后您的循环尝试从变量 listeCompanies
中获取下一项。但是在导航发生后,该变量中的元素句柄不再存在,因此会引发错误。这也是第一次迭代有效的原因。
解决方案
有多种方法可以解决这个问题。
- 立即从您的页面中提取数据(在使用循环之前)
- 使用第二个页面来执行 "loop navigation" 这样您的主页就不需要导航了
- "Refresh" 您的变量,方法是在调用
page.goBack
后重新执行选择器
选项 1:在进入循环之前提取数据
这是最简洁的方法。您一次提取第一页中的信息,然后迭代提取的数据。 nameLinkList
将是一个包含 name
和 link
值的数组(例如 [{name: '..', link: '..'}, {name: '..', link: '..'}]
)。也不需要在循环结束时调用 page.goBack
,因为数据已经被提取。
const nameLinkList = await page.$$eval(
'.list-firms > div.firm',
(firms => firms.map(firm => {
const a = firm.querySelector('.listing-body > h3 > a');
return {
name: a.innerText,
link: a.href
};
}))
);
for (const {name, link} of arr) {
await Promise.all([
page.waitForNavigation(),
page.goto(link),
page.waitForSelector('.firm-panel'),
]);
const info = await page.$eval('#info', e => e.innerText);
const data = [{
name: name,
information: info,
}];
}
选项 2:使用第二页
在这种情况下,您的浏览器将打开两个页面。第一个只用于读取数据,第二个用于导航。
const page2 = await browser.newPage();
for (const companie of listeCompanies ){
const name = await companie.$eval('.listing-body > h3 > a', name => name.innerText);
const link = await companie.$eval('.listing-body > h3 > a', link => link.href);
await Promise.all([
page2.goto(link),
page2.waitForSelector('.firm-panel'),
]);
const info = await page2.$eval('#info', e => e.innerText);
// ...
}
选项 3:"Refresh" 个选择器
在这里,您只需在返回 "main page" 后重新执行您的选择器。请注意,在我们替换数组时,for..of
必须更改为迭代器循环。
let listeCompanies = await page.$$('.list-firms > div.firm');
for (let i = 0; i < listeCompanies.length; i++){
// ...
await page.goBack();
listeCompanies = await page.$$('.list-firms > div.firm');
}
我建议使用选项 1,因为这也减少了必要的导航请求的数量,因此会加快您的脚本。