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

使用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;
问题原因与修复方案

核心问题

  1. map循环异步处理失效:$(".thing.linkflair.link").map()搭配async回调时,map不会等待异步操作完成,代码会直接执行后续逻辑,此时posts数组还未被填充。
  2. 全局posts变量污染:posts是全局变量,多请求场景下会累积历史数据,导致返回结果混乱。
  3. 递归返回值未合并:递归调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 01:30:28