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

使用Cheerio爬取文章标题时出现无限加载问题求助

问题描述

在Node.js环境中使用Cheerio爬取The Verge网站的文章标题时,出现谷歌浏览器无限加载且无内容显示的问题。Handlesbars视图引擎工作正常,控制台无报错信息,爬取目标类名为.c-entry-box--compact__title,无法定位问题根源。

相关代码(controller.js)
var express = require("express");
var router = express.Router();
var path = require("path");

var request = require("request");
var cheerio = require("cheerio");

var Comment = require("../models/Comment.js");
var Article = require("../models/Article.js");

router.get("/", function(req, res) {
  res.redirect("/articles");
});

router.get("/scrape", function(req, res) {
  request("http://www.theverge.com", function(error, response, html) {
    var $ = cheerio.load(html);
    var titlesArray = [];

    $(".c-entry-box--compact__title").each(function(i, element) {
      var result = {};

      result.title = $(this)
        .children("a")
        .text();
      result.link = $(this)
        .children("a")
        .attr("href");

      if (result.title !== "" && result.link !== "") {
        if (titlesArray.indexOf(result.title) == -1) {
          titlesArray.push(result.title);

          Article.count({ title: result.title }, function(err, test) {
            if (test === 0) {
              var entry = new Article(result);

              entry.save(function(err, doc) {
                if (err) {
                  console.log(err);
                } else {
                  console.log(doc);
                }
              });
            }
          });
        } else {
          console.log("Article already exists.");
        }
      } else {
        console.log("Not saved to DB, missing data");
      }
    });
    res.redirect("/");
  });
});
router.get("/articles", function(req, res) {
  Article.find()
    .sort({ _id: -1 })
    .exec(function(err, doc) {
      if (err) {
        console.log(err);
      } else {
        var artcl = { article: doc };
        res.render("index", artcl);
      }
    });
});

router.get("/articles-json", function(req, res) {
  Article.find({}, function(err, doc) {
    if (err) {
      console.log(err);
    } else {
      res.json(doc);
    }
  });
});

router.get("/clearAll", function(req, res) {
  Article.remove({}, function(err, doc) {
    if (err) {
      console.log(err);
    } else {
      console.log("removed all articles");
    }
  });
  res.redirect("/articles-json");
});

router.get("/readArticle/:id", function(req, res) {
  var articleId = req.params.id;
  var hbsObj = {
    article: [],
    body: []
  };

  Article.findOne({ _id: articleId })
    .populate("comment")
    .exec(function(err, doc) {
      if (err) {
        console.log("Error: " + err);
      } else {
        hbsObj.article = doc;
        var link = doc.link;
        request(link, function(error, response, html) {
          var $ = cheerio.load(html);

          $(".l-col__main").each(function(i, element) {
            hbsObj.body = $(this)
              .children(".c-entry-content")
              .children("p")
              .text();

            res.render("article", hbsObj);
            return false;
          });
        });
      }
    });
});
router.post("/comment/:id", function(req, res) {
  var user = req.body.name;
  var content = req.body.comment;
  var articleId = req.params.id;

  var commentObj = {
    name: user,
    body: content
  };

  var newComment = new Comment(commentObj);

  newComment.save(function(err, doc) {
    if (err) {
      console.log(err);
    } else {
      console.log(doc._id);
      console.log(articleId);

      Article.findOneAndUpdate(
        { _id: req.params.id },
        { $push: { comment: doc._id } },
        { new: true }
      ).exec(function(err, doc) {
        if (err) {
          console.log(err);
        } else {
          res.redirect("/readArticle/" + articleId);
        }
      });
    }
  });
});

module.exports = router;
排查与解决建议
  1. 替换已废弃的request库
    request包已停止维护,可能导致请求The Verge时无法获取正确的HTML内容,建议换成axios或node-fetch。以axios为例:
  • 安装:npm install axios
  • 修改scrape路由的请求代码:
// 替换request导入
const axios = require("axios");

router.get("/scrape", async function(req, res) {
  try {
    const response = await axios.get("http://www.theverge.com");
    const html = response.data;
    // 后续Cheerio处理逻辑不变
  } catch (error) {
    console.log("请求失败:", error);
    res.status(500).send("爬取失败");
  }
});
  1. 修复异步操作未等待的问题
    在/scrape路由中,数据库的count和save都是异步操作,直接在.each循环后重定向会导致数据库还未写入数据就跳转,页面自然无内容。改用async/await处理异步逻辑:
router.get("/scrape", async function(req, res) {
  try {
    const response = await axios.get("http://www.theverge.com");
    const html = response.data;
    const $ = cheerio.load(html);
    const titlesArray = [];

    // 把each循环改成map+Promise.all
    const scrapePromises = $(".c-entry-box--compact__title").map(async function(i, element) {
      const result = {};
      result.title = $(this).children("a").text().trim();
      result.link = $(this).children("a").attr("href");

      if (result.title && result.link) {
        if (!titlesArray.includes(result.title)) {
          titlesArray.push(result.title);
          const count = await Article.count({ title: result.title });
          if (count === 0) {
            const entry = new Article(result);
            await entry.save();
            console.log("保存文章:", result.title);
          } else {
            console.log("文章已存在:", result.title);
          }
        }
      } else {
        console.log("数据缺失,不保存");
      }
    }).get();

    await Promise.all(scrapePromises);
    res.redirect("/");
  } catch (error) {
    console.log("爬取出错:", error);
    res.status(500).send("爬取失败");
  }
});
  1. 验证DOM选择器有效性
    The Verge的页面结构可能已更新,先在scrape路由中打印选中元素的数量,确认选择器是否有效:
console.log("选中的标题元素数量:", $(".c-entry-box--compact__title").length);

如果数量为0,说明选择器失效,需要打开The Verge页面,用浏览器开发者工具重新定位文章标题的类名。

  1. 修复/readArticle路由的错误处理
    如果请求文章详情页失败(比如链接失效、网络错误),当前代码不会返回响应,导致浏览器无限等待。添加错误处理:
request(link, function(error, response, html) {
  if (error) {
    console.log("获取文章详情失败:", error);
    return res.status(500).send("无法加载文章内容");
  }
  // 后续Cheerio处理逻辑
});
  1. 修复/clearAll路由的异步问题
    Article.remove是异步操作,要在回调中执行重定向,确保数据库操作完成后再跳转:
router.get("/clearAll", function(req, res) {
  Article.remove({}, function(err, doc) {
    if (err) {
      console.log(err);
      return res.status(500).send("删除失败");
    }
    console.log("removed all articles");
    res.redirect("/articles-json");
  });
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 00:50:16