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

如何在网页爬取流程中正确使用Promise处理异步逻辑?

解决Promise处理网页爬取异步流程的问题

看起来你现在的核心问题是没有把异步网络请求正确包裹进Promise逻辑里,而且forEach循环不会等待内部的异步操作完成,导致后续代码(比如foodItemsRef.set(dbFoodItems))经常在爬取还没结束就提前执行了。我来一步步帮你梳理和修正:

问题拆解

  1. 你把ScrapeAsync定义在了$.get的回调函数内部,外部调用它时,网络请求大概率还没完成,自然拿不到正确的response数据。
  2. $.get本身是异步操作,你需要把它的执行逻辑和Promise绑定,而不是在请求回调里才临时创建Promise。
  3. forEach不会等待异步任务完成,所以你需要把每个爬取任务都转成Promise,再用Promise.all等待所有任务结束后再执行后续操作。

修正后的代码实现

先把单个商品的爬取逻辑封装成返回Promise的函数,再统一处理所有爬取任务:

// 封装单个商品的爬取逻辑,返回Promise
function scrapeItem(item) {
  return new Promise((resolve, reject) => {
    // 跳过已存在的商品,直接结束当前任务
    if (item in dbFoodItems) {
      resolve();
      return;
    }

    // 创建商品实例
    const Food = function(code) {
      this.code = code;
    };
    dbFoodItems[item] = new Food(item);

    // 构造请求URL
    const upc = item;
    const epoch = (new Date).getTime();
    const base = "https://www.realcanadiansuperstore.ca";
    const firstURL = `https://www.realcanadiansuperstore.ca/search/${epoch}/page/~item/${upc}/~sort/recommended/~selected/true`;

    // 第一次请求:获取商品详情页链接
    $.get(firstURL)
      .then(response => {
        const linkMatch = response.match(/<a href="(.*?)" class="product-name"/);
        if (!linkMatch) {
          throw new Error(`无法找到商品${item}的详情链接`);
        }
        const link = linkMatch[1];
        // 第二次请求:获取商品详情数据
        return $.get(base + link);
      })
      .then(response => {
        // 提取公司信息(先检查匹配结果是否存在)
        const companyMatch = response.match(/<span class="product-sub-title">\n\s\s\s\s\s\s(.*?)  <\/span>/);
        if (companyMatch) {
          dbFoodItems[item].company = companyMatch[1];
          console.log(`已爬取商品${item}的公司信息:${companyMatch[1]}`);
        } else {
          console.warn(`商品${item}未找到公司信息`);
        }
        resolve();
      })
      .catch(error => {
        console.error(`爬取商品${item}失败:`, error);
        // 即使单个任务失败,也不中断整体流程
        resolve();
      });
  });
}

// 页面切换触发的主函数
var buttonClicked = function() {
  console.log("Redirection");
  console.log("待爬取条码列表:", barcodeList);
  console.log("现有数据库:", dbFoodItems);

  // 把每个商品的爬取任务转成Promise,存入数组
  const scrapePromises = barcodeList.map(item => scrapeItem(item));

  // 等待所有爬取任务完成后,再执行后续操作
  Promise.all(scrapePromises)
    .then(() => {
      console.log("所有爬取任务完成!最终数据库:", dbFoodItems);
      foodItemsRef.set(dbFoodItems);
    })
    .catch(error => {
      console.error("爬取流程出现异常:", error);
      // 可选:即使部分失败,也保存已爬取的数据
      foodItemsRef.set(dbFoodItems);
    });
};

关键改动说明

  • 单个任务封装为Promise:把两次$.get的异步请求全包裹进Promise,确保只有请求完成并处理完数据后,Promise才会resolve。
  • 用map替代forEach:map会返回一个Promise数组,方便我们用Promise.all等待所有任务结束,这正是你需要的“全爬取完成后再执行下一步”的逻辑。
  • 添加错误处理:每个请求环节都做了异常捕获,避免单个商品爬取失败导致整个流程崩溃。
  • 正则匹配前置检查:先判断正则匹配结果是否存在,避免出现Cannot read property '[1]' of null的报错。

额外建议

如果爬取频率过高,可能会被网站反爬机制拦截,可以考虑给每个爬取任务添加间隔(比如用setTimeout控制请求间隔)。

内容的提问来源于stack exchange,提问作者Devansh Sharma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:42:21