如何优化基于已抓取URL批量拉取供应商商品详情的慢速代码
爬虫代码优化方案
性能瓶颈原因
当前代码运行慢的核心原因是详情页采用串行请求逻辑:25个商品详情页必须等待前一个请求、解析完全完成后才会发起下一个,总耗时是所有单个请求耗时的叠加。
优化后代码
import fetch from "isomorphic-fetch"; import cheerio from "cheerio"; // 域名提取为常量,避免重复硬编码 const BASE_URL = "https://www.supplier.co.uk"; // 单个商品详情页抓取逻辑抽离为独立函数 const fetchProductDetail = async (productUrl) => { try { const detailRes = await fetch(productUrl); const detailHtml = await detailRes.text(); const $detail = cheerio.load(detailHtml); return { pageSrc: productUrl, imageSrc: $detail("#product-main-image .main-image-inner:first-child img").attr("src"), name: $detail("#product-details dd:nth-child(2)").text().trim(), brand: $detail("#product-details dd:nth-child(4)").text().trim(), price: $detail("#product-details dd:nth-child(6)").text().trim() } } catch (err) { // 单个商品抓取失败打印日志,不影响整体返回 console.error(`商品${productUrl}抓取失败:`, err.message); return null; } } export const fetchNewProducts = async (page) => { try { const listRes = await fetch( `${BASE_URL}/newarrivals/?setPerPage=25&search_direction=asc&pageID=${page}` ); const listHtml = await listRes.text(); const $list = cheerio.load(listHtml); // 批量提取所有商品链接,不用硬编码1-25的索引,适配列表变化 const productUrls = []; $list("#product_listing > tbody tr td:nth-child(2) > a").each((_, el) => { productUrls.push(`${BASE_URL}${$list(el).attr("href")}`); }); // 并发请求所有详情页,大幅压缩耗时 const productTasks = productUrls.map(url => fetchProductDetail(url)); const rawProducts = await Promise.all(productTasks); // 过滤掉抓取失败的空值 return rawProducts.filter(Boolean); } catch (err) { console.error("列表页抓取失败:", err.message); return []; } }; // 注意:原代码混用ES模块和CommonJS导出规范,这里统一用ES模块导出,如果你的项目用CommonJS可以改为下面的写法 // module.exports = { fetchNewProducts }
主要优化点
- 采用并发请求替代串行请求,在站点无并发限制的情况下,整体耗时可以压缩到原来的1/10~1/20
- 抽离单个商品抓取逻辑,代码结构更清晰,后续修改规则更方便
- 修复原代码模块导出规范混用的问题,避免运行报错
- 补全全链路错误处理,单个商品/列表页抓取失败不会直接返回空,也有日志方便排查问题
- 优化列表页链接提取逻辑,不用硬编码遍历1-25的索引,适配列表页商品数量变动的场景
- 统一提取域名为常量,所有文本结果加trim()去除多余空格,数据更规范
- 如果需要控制并发数避免被站点封IP,可以自己实现简单的并发队列,或者引入对应工具包实现分批并发。
内容的提问来源于stack exchange,提问作者Liiaam93
相关产品推荐
相关产品推荐

