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

Node.js Request+Cheerio循环爬取CPU数据的异步变量问题求助

解决Node.js循环中异步回调共享变量的问题

这个问题是JavaScript里异步操作与作用域的经典坑,我来帮你拆解原因和解决方案:

问题根源

  1. 变量作用域问题:你用var i声明循环变量,var是函数级作用域,整个循环里只有一个i变量。当异步的request回调执行时,循环已经全部跑完了,此时i的值已经变成了box.length(也就是24),所以所有回调里打印的i都是24。
  2. 对象引用共享:你在循环里用var cpu声明对象,同样因为var的作用域,所有迭代共享同一个cpu对象引用。当后续异步回调修改这个对象时,前面已经push到allCpu里的条目也会被覆盖,最终所有数据都是最后一个CPU的信息。

修复方案

我会用ES6的块级作用域和Promise来解决这两个问题,同时优化异步流程避免回调地狱:

const request = require('request');
const cheerio = require('cheerio');
const fs = require('fs');

const url = 'https://www.alza.sk/cpu-procesory/18842843-p1.htm';
let allCpu = [];

// 封装request为Promise,方便异步控制
const requestPromise = (url) => {
  return new Promise((resolve, reject) => {
    request(url, (error, response, html) => {
      if (error) reject(error);
      else resolve(html);
    });
  });
};

requestPromise(url)
  .then(html => {
    const $ = cheerio.load(html);
    const box = $('#boxc .box');
    // 用let声明i,创建块级作用域
    const fetchCpuPromises = [];
    for (let i = 0; i < box.length; i++) {
      console.log(i); // 正常输出0-23
      // 用let声明cpu,每个迭代有独立的对象
      let cpu = {
        meno: '',
        link: '',
        socket: '',
        radaCpu: '',
        xJadier: '',
        frekvenciaCpu: '',
        maxFrekvenciaCpu: ''
      };
      const $boxItem = box.eq(i);
      cpu.meno = $boxItem.find('.fb a').text();
      cpu.link = $boxItem.find('.fb a').attr('href');
      
      // 把每个CPU的请求Promise存入数组
      const fetchPromise = requestPromise('https://www.alza.sk/' + cpu.link)
        .then(detailHtml => {
          const $socket = cheerio.load(detailHtml);
          const params = $socket('#popis .params .groupValues .row a');
          params.each(function() {
            const text = $(this).text();
            const value = $(this).parents('.row').find('.value').text();
            switch(text) {
              case 'Socket':
                cpu.socket = value;
                break;
              case 'Rad procesora':
                cpu.radaCpu = value;
                break;
              case 'Počet jadier procesora':
                cpu.xJadier = value;
                break;
              case 'Frekvencie procesora':
                cpu.frekvenciaCpu = value;
                break;
              case 'Maximálna frekvencia (OC)':
                cpu.maxFrekvenciaCpu = value;
                break;
            }
          });
          return cpu; // 返回处理好的cpu对象
        })
        .catch(err => {
          console.error(`获取CPU详情失败: ${err}`);
          return null; // 出错时返回null,不影响整体流程
        });
      
      fetchCpuPromises.push(fetchPromise);
    }
    
    // 等待所有CPU详情请求完成
    return Promise.all(fetchCpuPromises);
  })
  .then(cpuList => {
    // 过滤掉出错的null条目
    allCpu = cpuList.filter(cpu => cpu !== null);
    // 写入文件
    fs.writeFile('output.json', JSON.stringify(allCpu, null, 4), err => {
      if (err) console.error('写入文件失败:', err);
      else console.log('File successfully written! - Check your project directory for the output.json file');
    });
  })
  .catch(err => {
    console.error('获取CPU列表失败:', err);
  });

关键优化点

  1. 块级作用域:用let i和let cpu替代var,确保每个循环迭代有独立的变量实例,避免回调共享变量。
  2. Promise封装:把request封装成Promise,用Promise.all等待所有异步请求完成,这样能保证所有CPU数据都获取到后再写入文件,解决了原来靠i == box.length -1判断的不可靠问题(因为异步请求的完成顺序不一定和循环顺序一致)。
  3. 代码可读性:用switch替代多个if判断,结构更清晰;用Promise链式调用替代嵌套回调,避免回调地狱。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 10:09:28