You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Puppeteer中如何同步执行for循环?解决循环异步异常问题

Puppeteer循环无法同步执行的问题修复方案

我正在编写一个Puppeteer脚本,流程为:登录页面后进入商品列表页,获取带有[data-pid]属性的元素列表,逐个点击列表中的链接,跳转后检查是否存在#hunt > img元素,若存在则点击,随后返回上一页。

当前脚本中的for循环本该同步执行,但第一次循环正常,从第二次开始循环会立即执行所有剩余迭代,不再等待每一步完成。现有代码如下:

import puppeteer from 'puppeteer';

(async () => {
    const browser = await puppeteer.launch({headless:false});
    const page = await browser.newPage();

    // Login
    await page.goto('https://www.acherryontop.com/user/log_in', {waitUntil: 'domcontentloaded'});
    
    let loginButton = await page.waitForSelector('#action2');
    await page.type('#email', 'email@email.com');
    await page.type('#password', 'password');

    await loginButton.click();
    await page.waitForNavigation();

    // Start
    await page.goto('https://www.acherryontop.com/shop/company/stamperia_intl', {waitUntil: 'domcontentloaded'});

    let elemList = await page.$$('[data-pid]');

    for (let i = 0; i <= elemList.length; i++) {

        let pointsExist = !! await page.$('#hunt_progress_number');

        if (pointsExist) {
            let currentItem = elemList[i];

            // This has to be wrapped in a try or it errors on second iteration
            try {
                await currentItem.click({waitUntil: 'domcontentloaded'});
            } catch {
                console.log('Failed to click');
            }

            // If element is here, click on it
            try {
                let point = await page.$('#hunt > img');
                await point.click({waitUntil: 'domcontentloaded'});
            } catch {
                console.log('Point not found, going back...');
            }

            // Go back and click on the next item
            await page.goBack({waitUntil: 'domcontentloaded'});
        }
    }
})();

问题根源

核心问题是:从商品列表页跳转再返回后,之前通过page.$$()获取的elemList中的元素句柄已失效。页面导航会重置DOM环境,旧元素引用不再指向当前页面的真实元素,导致后续循环的点击操作要么报错要么无响应,看起来像是循环“跳过等待直接执行”。

另外循环条件i <= elemList.length会导致最后一次迭代时elemList[i]为undefined,也是报错的诱因,应改为i < elemList.length。

修复方案

不要提前缓存所有元素句柄,每次循环开始时重新获取当前页面的目标元素,确保操作的是有效DOM节点。修改后的代码如下:

import puppeteer from 'puppeteer';

(async () => {
    const browser = await puppeteer.launch({headless: false});
    const page = await browser.newPage();

    // 登录流程
    await page.goto('https://www.acherryontop.com/user/log_in', {waitUntil: 'domcontentloaded'});
    
    const loginButton = await page.waitForSelector('#action2');
    await page.type('#email', 'email@email.com');
    await page.type('#password', 'password');

    await loginButton.click();
    await page.waitForNavigation();

    // 进入商品列表页
    await page.goto('https://www.acherryontop.com/shop/company/stamperia_intl', {waitUntil: 'domcontentloaded'});

    // 获取商品总数,避免每次循环重复查询
    const itemCount = await page.$$eval('[data-pid]', elems => elems.length);

    for (let i = 0; i < itemCount; i++) {
        const pointsExist = !!await page.$('#hunt_progress_number');

        if (pointsExist) {
            try {
                // 每次通过:nth-of-type选择器定位当前元素,确保是当前页面的有效节点
                await page.click(`[data-pid]:nth-of-type(${i + 1})`, {waitUntil: 'domcontentloaded'});
            } catch (err) {
                console.log('点击商品失败:', err.message);
                // 点击失败后返回列表页,继续下一次循环
                await page.goBack({waitUntil: 'domcontentloaded'});
                continue;
            }

            // 检查并点击目标元素,使用waitForSelector确保元素存在
            try {
                const pointElem = await page.waitForSelector('#hunt > img', {timeout: 3000});
                await pointElem.click({waitUntil: 'domcontentloaded'});
            } catch {
                console.log('未找到目标元素,返回列表页...');
            }

            // 返回列表页并等待加载完成
            await page.goBack({waitUntil: 'domcontentloaded'});
            // 等待当前索引的商品元素重新渲染,确保下一次点击有效
            await page.waitForSelector(`[data-pid]:nth-of-type(${i + 1})`);
        }
    }

    // 执行完毕关闭浏览器
    await browser.close();
})();

关键修改点

  • 避免元素句柄失效:不再提前缓存elemList,每次循环通过:nth-of-type选择器定位当前元素,保证操作的是当前页面的有效DOM节点。
  • 修正循环边界:将i <= elemList.length改为i < itemCount,避免越界访问。
  • 强化错误处理:点击商品失败后,通过continue跳过当前迭代,防止后续逻辑出错。
  • 可靠的元素等待:用page.waitForSelector替代page.$,设置超时时间确保元素存在后再操作,避免无限等待。
  • 列表页渲染等待:返回列表页后,等待目标元素重新渲染,提升下一次点击的可靠性。

内容的提问来源于stack exchange,提问作者Zach Wulf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 07:34:54