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

Node.js爬虫请求间隔设置:避免IP封禁的实现方法

如何为网页爬虫的循环请求添加时间间隔?

你当前的代码问题在于:for循环里的request是异步调用,会瞬间把所有URL的请求都发出去,完全没有间隔,这确实很容易触发目标网站的反爬机制封禁IP。要实现每次请求间隔2分钟,我们可以通过递归处理或者async/await配合延迟函数来控制请求节奏,下面给你两种可行的实现方案:

方案一:递归处理URL队列

这种方式适配你现有回调风格的代码,通过递归逐个处理URL,每次完成当前请求后延迟2分钟再处理下一个:

cn.query('SELECT url FROM models', function (err, rows, field) {
  if (err) {
    console.error('查询URL失败:', err);
    return;
  }
  
  // 把查询结果转成URL数组
  const urls = rows.map(item => item.url);
  const delayTime = 2 * 60 * 1000; // 2分钟,单位毫秒

  // 定义递归函数,逐个处理URL
  function processNextUrl(index) {
    // 所有URL处理完成,退出递归
    if (index >= urls.length) {
      console.log('✅ 所有URL爬取任务完成');
      return;
    }

    const currentUrl = urls[index];
    request(currentUrl, (err, res, body) => {
      if (!err && res.statusCode === 200) {
        const $ = cheerio.load(body);
        // 这里保留你原有的数据解析逻辑(注意arr变量要确保正确定义)
        let sql = "INSERT INTO prodchar (year, Display, displaysize, os, Chipset, cpu, gpu, memoryslot, Storage, PrimaryCam, videocam, FrontCam, wlan, bluetooth, batdesc) VALUES ?";
        
        cn.query(sql, [[arr]], function(dbErr, dbRes) {
          if (dbErr) {
            console.error(`插入数据失败(URL: ${currentUrl}):`, dbErr);
          } else {
            console.log(`✅ 数据插入成功(URL: ${currentUrl})`, dbRes);
            // 执行去重操作
            cn.query(
              "DELETE n1 FROM spec n1, spec n2 WHERE n1.id > n2.id AND n1.name = n2.name",
              (dupErr) => {
                if (dupErr) console.error('去重操作失败:', dupErr);
              }
            );
          }

          // 延迟2分钟后处理下一个URL
          setTimeout(() => {
            processNextUrl(index + 1);
          }, delayTime);
        });
      } else {
        console.error(`请求失败(URL: ${currentUrl}):`, err || `状态码: ${res?.statusCode}`);
        // 即使请求失败,也延迟后继续处理下一个(也可以改成重试逻辑)
        setTimeout(() => {
          processNextUrl(index + 1);
        }, delayTime);
      }
    });
  }

  // 启动任务,从第一个URL开始
  processNextUrl(0);
});

方案二:使用async/await + Promise包装(更清晰易维护)

如果你愿意稍微改造代码风格,用async/await可以让异步逻辑更直观,避免回调地狱:

// 先把request和数据库操作包装成Promise,方便用async/await
function requestWithPromise(url) {
  return new Promise((resolve, reject) => {
    request(url, (err, res, body) => {
      if (err) reject(err);
      else resolve({ res, body });
    });
  });
}

function queryWithPromise(sql, params) {
  return new Promise((resolve, reject) => {
    cn.query(sql, params, (err, res) => {
      if (err) reject(err);
      else resolve(res);
    });
  });
}

// 延迟函数
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// 主爬虫函数
async function startCrawling() {
  try {
    // 查询所有URL
    const rows = await queryWithPromise('SELECT url FROM models');
    const urls = rows.map(item => item.url);
    const delayTime = 2 * 60 * 1000; // 2分钟

    for (const url of urls) {
      try {
        console.log(`开始处理URL: ${url}`);
        const { res, body } = await requestWithPromise(url);
        
        if (res.statusCode === 200) {
          const $ = cheerio.load(body);
          // 数据解析逻辑(确保arr变量正确)
          const sql = "INSERT INTO prodchar (year, Display, displaysize, os, Chipset, cpu, gpu, memoryslot, Storage, PrimaryCam, videocam, FrontCam, wlan, bluetooth, batdesc) VALUES ?";
          await queryWithPromise(sql, [[arr]]);
          console.log(`✅ 数据插入成功: ${url}`);
          
          // 执行去重
          await queryWithPromise("DELETE n1 FROM spec n1, spec n2 WHERE n1.id > n2.id AND n1.name = n2.name");
        } else {
          console.warn(`⚠️ 请求状态异常: ${url}, 状态码: ${res.statusCode}`);
        }
      } catch (err) {
        console.error(`❌ 处理URL失败: ${url}`, err);
      } finally {
        // 不管成功失败,都延迟2分钟再处理下一个
        console.log(`等待2分钟后处理下一个URL...`);
        await delay(delayTime);
      }
    }

    console.log('🎉 所有URL处理完成!');
  } catch (err) {
    console.error('❌ 爬虫启动失败:', err);
  }
}

// 启动爬虫
startCrawling();

额外建议

  • 除了控制请求间隔,建议给request添加浏览器风格的请求头(比如User-Agent),模拟真实用户请求,降低被识别为爬虫的概率:
    request({
      url: currentUrl,
      headers: {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'
      }
    }, (err, res, body) => { /* ... */ });
    
  • 如果目标网站反爬较严格,还可以考虑加入重试机制(比如某个请求失败后,重试2-3次再跳过)。
  • 2分钟的间隔是比较保守的,你可以根据目标网站的响应情况适当调整,但一定要避免短时间内大量请求。

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

相关产品推荐
方舟 Agent Plan

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

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