如何在Node.js中结合request与cheerio实现同步调用?
解决异步导致的分类数据错误问题
核心问题根源
你用全局变量current_title跟踪类别,但异步操作(request请求、cheerio解析)的执行时机晚于current_title的更新,导致后续异步回调拿到的是已经被修改后的类别值,最终把数据塞错了分类。
具体解决方案
1. 移除全局变量,绑定类别与异步任务
不要用全局变量存当前类别,而是在处理每个类别的时候,把类别作为上下文参数传递给异步操作,确保每个异步任务都能拿到自己所属的类别。
错误代码示例(问题根源)
let current_title; // 遍历文本文件中的类别数据 for (const item of categoryItems) { current_title = item.title; // 异步请求解析,回调时current_title已被后续循环修改 request(item.url, (err, res, body) => { const $ = cheerio.load(body); result[current_title].push(/* 解析后的数据 */); }); }
修正后的串行处理代码(async/await版)
const fs = require('fs').promises; const request = require('request'); const cheerio = require('cheerio'); async function processCategories(categoryItems) { const result = {}; // 用for...of循环,确保异步操作串行执行 for (const item of categoryItems) { const title = item.title; // 初始化当前类别的数组 if (!result[title]) result[title] = []; // 把request包装成Promise,配合await使用 const body = await new Promise((resolve, reject) => { request(item.url, (err, res, body) => { if (err) reject(err); resolve(body); }); }); // 解析数据并插入对应类别 const $ = cheerio.load(body); const parsedData = $('selector').text(); // 替换成你的解析逻辑 result[title].push(parsedData); } // 写入JSON文件 await fs.writeFile('./result.json', JSON.stringify(result, null, 2)); }
2. 用Promise化请求库简化代码
request库已停止维护,推荐用axios这类原生支持Promise的库,代码更简洁:
const axios = require('axios'); const fs = require('fs').promises; const cheerio = require('cheerio'); async function processCategories(categoryItems) { const result = {}; for (const item of categoryItems) { const title = item.title; if (!result[title]) result[title] = []; try { const res = await axios.get(item.url); const $ = cheerio.load(res.data); const parsedData = $('selector').text(); // 替换成你的解析逻辑 result[title].push(parsedData); } catch (err) { console.error(`处理${title}失败:`, err); } } await fs.writeFile('./result.json', JSON.stringify(result, null, 2)); }
3. 并行处理提升效率(可选)
如果不需要严格串行,可通过Promise.all并行处理,但要确保每个异步任务携带对应类别信息:
async function processCategories(categoryItems) { const result = {}; // 先初始化所有类别 categoryItems.forEach(item => result[item.title] = []); // 生成所有异步任务,每个任务返回{类别, 数据} const tasks = categoryItems.map(async item => { const res = await axios.get(item.url); const $ = cheerio.load(res.data); const parsedData = $('selector').text(); return { title: item.title, data: parsedData }; }); // 等待所有任务完成,将数据对应到类别 const taskResults = await Promise.all(tasks); taskResults.forEach(item => result[item.title].push(item.data)); await fs.writeFile('./result.json', JSON.stringify(result, null, 2)); }
更优实现建议
- 彻底抛弃全局变量:任何需要和异步任务绑定的上下文数据,都通过参数或闭包传递,避免状态污染。
- 使用现代异步API:优先用
async/await替代回调,用fs.promises、axios这类支持Promise的模块,代码可读性更强。 - 完善错误处理:给异步操作加上
try/catch,避免单个请求失败导致整个任务崩溃。 - 模块化拆分:把HTML解析、文件读写拆成独立函数,比如
parseHtml($)、saveResult(result),代码结构更清晰。
内容的提问来源于stack exchange,提问作者bot69
相关产品推荐
相关产品推荐

