优化Cheerio抓取循环 单次遍历获取90分钟内最后比赛比分
问题场景
在Google Apps Script环境下使用Cheerio开展网页数据抓取工作,目标页面为足球赛事进球记录页,进球数据存储在class属性为scorer-info的ul列表中:
- 每个li子节点对应一次进球事件
- 节点内包含class为
score的span元素存储当前比分 - li下class为
scorer的span内包含class为minute的span元素存储进球分钟数 - 分钟与比分按进球顺序一一对应
需求说明
匹配进球分钟数小于等于90时对应的最后一个比分值,以提供的示例数据为例,符合要求的结果为67分钟对应的比分2-2。
原有实现问题
原有代码逻辑为分别通过选择器独立获取所有span.minute、span.score元素,经过两次each遍历将提取处理后的分钟值、比分值存入两个独立二维数组,再通过第三次for循环遍历数组逐次更新符合分钟≤90条件的比分值,存在明显缺陷:
- 需要维护多个额外存储数组,内存占用高
- 三轮遍历+两次独立DOM查询带来不必要的执行开销
- 靠索引对齐两个独立元素集合的逻辑鲁棒性差
- 冗余代码多,可维护性低
原有可正常运行的代码如下:
function score() { var ss = SpreadsheetApp.getActive().getSheetByName('copy'); var response = UrlFetchApp.fetch('URL URL URL URL', {muteHttpExceptions: true}); if (response.getResponseCode() == 404) { } else { var contentText = response.getContentText(); var $ = Cheerio.load(contentText); var list_minutes = []; var list_score = []; var minute_goal = $('ul.scorer-info > li > span.scorer > span.minute'); var score_goal = $('ul.scorer-info > li > span.score'); minute_goal.each((index, element) => {list_minutes.push([($(element).text().trim()).substring(0, ($(element).text().trim()).indexOf("'"))]);}); score_goal.each((index, element) => {list_score.push([($(element).text().trim()).replace(/ /g,'')]);}); var before_90 = '0-0'; var i=0; var max = list_minutes.length for(i; i<max; i++){ if (list_minutes[i][0] <= 90) { before_90 = list_score[i][0]; } } Logger.log(before_90) } }
原有代码运行后可正确输出结果2-2。
优化实现方案
直接以单条进球事件对应的li节点为最小遍历单位,单次DOM遍历即可完成所有判断和结果更新,完全去掉中间存储数组,DOM查询和遍历总次数从3次降至1次。
优化后代码如下:
function score() { var ss = SpreadsheetApp.getActive().getSheetByName('copy'); var response = UrlFetchApp.fetch('URL URL URL URL', {muteHttpExceptions: true}); if (response.getResponseCode() === 404) return; const $ = Cheerio.load(response.getContentText()); // 初始化默认比分 let before_90 = '0-0'; // 单次遍历所有进球条目,直接在条目上下文内提取所需数据 $('ul.scorer-info > li').each((_, entry) => { const $entry = $(entry); // 提取当前进球的分钟数 const minuteRaw = $entry.find('span.minute').text().trim(); const minute = Number(minuteRaw.substring(0, minuteRaw.indexOf("'"))); // 符合时间条件就更新比分,按进球顺序遍历后最终留存的就是最晚的合规比分 if (minute <= 90) { before_90 = $entry.find('span.score').text().trim().replace(/\s/g, ''); } }); Logger.log(before_90); return before_90; }
优化说明:
- 以li为遍历单位,在单个节点上下文内同时查询分钟和比分,避免了分开查询元素集合再靠索引对齐的不可靠逻辑
- 完全移除两个中间存储数组,减少不必要的内存占用
- 仅执行1次DOM遍历,脚本执行速度提升明显
- 简化了404分支的空代码块,逻辑更简洁
- 文本处理规则和原有逻辑完全一致,输出结果和原代码100%兼容,示例场景下仍返回
2-2
内容的提问来源于stack exchange,提问作者Digital Farmer
相关产品推荐
相关产品推荐

