如何在网页爬取流程中正确使用Promise处理异步逻辑?
解决Promise处理网页爬取异步流程的问题
看起来你现在的核心问题是没有把异步网络请求正确包裹进Promise逻辑里,而且forEach循环不会等待内部的异步操作完成,导致后续代码(比如foodItemsRef.set(dbFoodItems))经常在爬取还没结束就提前执行了。我来一步步帮你梳理和修正:
问题拆解
- 你把
ScrapeAsync定义在了$.get的回调函数内部,外部调用它时,网络请求大概率还没完成,自然拿不到正确的response数据。 $.get本身是异步操作,你需要把它的执行逻辑和Promise绑定,而不是在请求回调里才临时创建Promise。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(.*?)&nbsp; <\/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
相关产品推荐
相关产品推荐

