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
相关产品推荐
相关产品推荐

