使用Cheerio爬取Reddit子版块的异步函数在Express中返回undefined
问题描述
单独运行爬虫脚本时一切正常(手动传入URL、用fs模块写入JSON文件,执行node script_name.js),但在Express的GET请求中调用时返回undefined。前端允许用户输入Reddit子版块名称,问题就出在这个交互流程里。
Express控制器代码
const run = require("../run"); requestPosts: async (req, res) => { try { const { subreddit } = req.body; const response = await run(subreddit); //console.log(response); res.json(response); } catch (error) { console.error(error); } },
Cheerio爬虫函数代码
const axios = require("axios"); const { load } = require("cheerio"); let posts = []; async function getImage(postLink) { const { data } = await axios(postLink); const $ = load(data); return $("a.post-link").attr("href"); } async function run(url) { try { console.log(url); const { data } = await axios(url); const $ = load(data); $(".thing.linkflair.link").map(async (i, e) => { const title = $(e) .find(".entry.unvoted .top-matter .title .title") .text(); const user = $(e) .find(".entry.unvoted .top-matter .tagline .author") .text(); const profileLink = `https://old.reddit.com/user/${user}`; const postLink = `https://old.reddit.com/${$(e).find("a").attr("href")}`; // const thumbail = $(e).find("a img").attr("src"); const image = await getImage(postLink); posts.push({ id: i + 1, title, postLink, image, user: { user, profileLink }, }); }); const nextPage = $(".next-button a").attr("href"); if (nextPage) { await run(nextPage); } else { return posts; } } catch (error) { console.error(error); } } module.exports = run;
问题原因与修复方案
核心问题
map循环异步处理失效:$(".thing.linkflair.link").map()搭配async回调时,map不会等待异步操作完成,代码会直接执行后续逻辑,此时posts数组还未被填充。- 全局
posts变量污染:posts是全局变量,多请求场景下会累积历史数据,导致返回结果混乱。 - 递归返回值未合并:递归调用
run(nextPage)时,没有把递归获取的帖子数据合并到当前数组,最终可能返回不完整或空数据。
修复步骤
1. 用Promise.all替代map处理异步遍历
将map替换为Array.from结合Promise.all,确保所有帖子的异步请求完成后再继续执行:
// 替换原有map代码块 const elements = $(".thing.linkflair.link"); const pagePosts = await Promise.all(Array.from(elements).map(async (e, i) => { const title = $(e) .find(".entry.unvoted .top-matter .title .title") .text(); const user = $(e) .find(".entry.unvoted .top-matter .tagline .author") .text(); const profileLink = `https://old.reddit.com/user/${user}`; const postLink = `https://old.reddit.com/${$(e).find("a").attr("href")}`; const image = await getImage(postLink); return { id: posts.length + i + 1, // 修正id,避免递归时重复 title, postLink, image, user: { user, profileLink }, }; })); posts.push(...pagePosts);
2. 将posts改为局部变量
把posts移到run函数内部,每次调用都初始化新数组,避免全局污染:
async function run(url) { let posts = []; // 移至函数内部 try { // 原有逻辑... } catch (error) { console.error(error); throw error; // 抛出错误让Express捕获 } }
3. 正确合并递归返回的结果
递归调用时,将返回的帖子数组合并到当前posts中:
if (nextPage) { const nextPosts = await run(nextPage); posts.push(...nextPosts); } return posts; // 无论是否有下一页,最终都返回完整数组
4. 正确抛出异常
在catch块中抛出错误,确保Express的try/catch能捕获到异常:
catch (error) { console.error(error); throw error; }
修复后的完整run函数
const axios = require("axios"); const { load } = require("cheerio"); async function getImage(postLink) { const { data } = await axios(postLink); const $ = load(data); return $("a.post-link").attr("href"); } async function run(url) { let posts = []; try { console.log(url); const { data } = await axios(url); const $ = load(data); const elements = $(".thing.linkflair.link"); const pagePosts = await Promise.all(Array.from(elements).map(async (e, i) => { const title = $(e) .find(".entry.unvoted .top-matter .title .title") .text(); const user = $(e) .find(".entry.unvoted .top-matter .tagline .author") .text(); const profileLink = `https://old.reddit.com/user/${user}`; const postLink = `https://old.reddit.com/${$(e).find("a").attr("href")}`; const image = await getImage(postLink); return { id: posts.length + i + 1, title, postLink, image, user: { user, profileLink }, }; })); posts.push(...pagePosts); const nextPage = $(".next-button a").attr("href"); if (nextPage) { const nextPosts = await run(nextPage); posts.push(...nextPosts); } return posts; } catch (error) { console.error(error); throw error; } } module.exports = run;
额外注意点
Express的requestPosts是GET请求,但使用了req.body获取参数。GET请求通常用req.query获取参数,或者将接口改为POST请求。如果保留GET方式,需将const { subreddit } = req.body;改为const { subreddit } = req.query;,前端也要对应调整传参方式。
内容的提问来源于stack exchange,提问作者mongu
相关产品推荐
相关产品推荐

