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

如何在Puppeteer并发任务中关闭标签后自动打开新标签?

问题:Puppeteer并发页面处理中手动关闭页面后的自动补位问题

我用Puppeteer实现并发页面处理逻辑时遇到了问题:当前executingFunctions中的页面会持续重载,一旦某页面加载到符合要求的HTML,就会与之交互、关闭标签并打开新标签。现在需要实现:如果手动关闭某个还没满足条件的页面,要自动打开新标签,同时不影响其他页面的重载和任务队列的新增。

相关代码

concurrentOrders.js

export default async function concurrentOrders(limit, links) {
  const executingFunctions = [];

  let currentIndex = 0;

  while (currentIndex < links.length) {
    while (executingFunctions.length < limit && currentIndex < links.length) {
      const link = links[currentIndex];
      currentIndex++;

      const promise = createBuyOrderPromise(...link).finally(() => {
        const index = executingFunctions.indexOf(promise);
        if (index !== -1) executingFunctions.splice(index, 1);
      });
      executingFunctions.push(promise);
    }

    await Promise.race(executingFunctions);
  }
}

createBuyOrderPromise.js

export default async function createBuyOrderPromise(link) {
  return new Promise(async (resolve, reject) => {
    try {
      const page = await makeConnection();  // 通过webSocketUrl连接已有浏览器
      await gotoMarketItemPage(page, link);
      await closeTabPage(page);     // 关闭标签页
      resolve();
    } catch () {
      reject();
    }
  });
}

gotoMarketItemPage.js

export default async function gotoMarketItemPage(page) {
  try {
    await page.goto(page,
      {
        waitUntil: "domcontentloaded",
        timeout: 0,
      }
    );

    const image = await page.$(itemImageSelector);

    if (image) elementFound = true;
    if (!image) await page.reload();
  }
}

之前的尝试及问题

我曾在gotoMarketItemPage中添加以下代码并传入reject函数:

await page.on("close", () => {
  reject();
});

但这么做会导致:手动关闭一个未加载完成的页面时,其他正在重载的页面也会停止,而且任务队列不再新增新页面。

解决方案

1. 修正页面关闭事件的监听逻辑

首先,page.on是事件绑定,不需要await,而且要确保页面关闭时只触发当前Promise的reject,不影响其他任务。用once代替on避免重复绑定,同时在任务正常完成后移除监听:

export default async function createBuyOrderPromise(link) {
  return new Promise(async (resolve, reject) => {
    let page;
    let handleClose;
    try {
      page = await makeConnection();
      
      // 绑定页面关闭事件,仅触发一次
      handleClose = () => reject(new Error("页面被手动关闭"));
      page.once("close", handleClose);

      await gotoMarketItemPage(page, link);
      
      // 任务正常完成,移除关闭监听
      page.off("close", handleClose);
      
      await closeTabPage(page);
      resolve();
    } catch (err) {
      reject(err);
    } finally {
      // 确保清理监听,避免内存泄漏
      if (page && handleClose) {
        page.off("close", handleClose);
      }
    }
  });
}

2. 完善页面循环重载逻辑

原代码中elementFound未定义,且没有持续重载的循环逻辑,修改后让页面持续重载直到找到目标元素或被手动关闭:

export default async function gotoMarketItemPage(page, link) {
  while (true) {
    try {
      await page.goto(link, {
        waitUntil: "domcontentloaded",
        timeout: 0,
      });

      const image = await page.$(itemImageSelector);
      if (image) break; // 找到元素,退出循环
      
      // 未找到元素,重载页面
      await page.reload({ waitUntil: "domcontentloaded", timeout: 0 });
    } catch (err) {
      // 页面被手动关闭时,抛出错误让上层处理
      if (err.message.includes("页面被手动关闭")) throw err;
      // 其他错误(如网络问题),继续重试
      continue;
    }
  }
}

3. 优化并发任务补充逻辑

修改concurrentOrders的循环条件,确保所有任务(包括异常结束的)都处理完毕,且异常结束的任务会自动被替换:

export default async function concurrentOrders(limit, links) {
  const executingFunctions = [];
  let currentIndex = 0;

  // 持续运行直到所有链接处理完成且无正在执行的任务
  while (currentIndex < links.length || executingFunctions.length > 0) {
    // 补充任务到并发上限
    while (executingFunctions.length < limit && currentIndex < links.length) {
      const link = links[currentIndex];
      currentIndex++;

      const promise = createBuyOrderPromise(link)
        .finally(() => {
          const index = executingFunctions.indexOf(promise);
          if (index !== -1) executingFunctions.splice(index, 1);
        });
      executingFunctions.push(promise);
    }

    // 等待任意一个任务完成(无论成功或失败)
    await Promise.race(executingFunctions);
  }
}

关键说明

  • 用page.once绑定关闭事件,避免重复触发导致的异常。
  • 任务正常完成后移除监听,防止后续页面关闭误触发当前Promise的reject。
  • 循环重载逻辑确保页面持续尝试直到找到目标元素或被手动关闭。
  • 并发逻辑中,无论任务是resolve还是reject,finally块都会将其从执行队列移除,Promise.race触发后会自动补充新任务,不影响其他正在运行的页面。

内容的提问来源于stack exchange,提问作者Mini elephant

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 07:51:04