一尘不染

人偶执行上下文被破坏,最有可能是由于导航

node.js

当我在另一个页面上获取数据时,我会在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);
}

我设法只获得了第一家公司的数据。


阅读 342

收藏
2020-07-07

共1个答案

一尘不染

问题

该错误表示您正在访问由于导航而变得过时/无效的数据。错误在您的脚本中引用了变量listeCompanies

const listeCompanies = await page.$$('.list-firms > div.firm');

首先,在循环中使用此变量,然后通过page.goto和进行导航,然后循环尝试从变量中获取下一项listeCompanies。但是在导航发生后,该变量中的元素句柄不再存在,因此引发了错误。这就是为什么第一次迭代有效的原因。

有多种解决方法。

  1. 立即从页面中提取数据(使用循环之前)
  2. 使用第二页进行“循环导航”,以便您的主页无需导航
  3. 通过在调用后重新执行选择器来“刷新”变量 page.goBack

选项1:在进入循环之前提取数据

这是最干净的方法。您一次提取第一页中的信息,然后遍历提取的数据。在nameLinkList将与一个数组namelink值(例如[{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:“刷新”选择器

返回“主页”后,您只需在这里重新执行选择器即可。请注意,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,因为这也减少了必要的导航请求,因此可以加快脚本的速度。

2020-07-07