使用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;
排查与解决建议
- 替换已废弃的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("爬取失败"); } });
- 修复异步操作未等待的问题
在/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("爬取失败"); } });
- 验证DOM选择器有效性
The Verge的页面结构可能已更新,先在scrape路由中打印选中元素的数量,确认选择器是否有效:
console.log("选中的标题元素数量:", $(".c-entry-box--compact__title").length);
如果数量为0,说明选择器失效,需要打开The Verge页面,用浏览器开发者工具重新定位文章标题的类名。
- 修复/readArticle路由的错误处理
如果请求文章详情页失败(比如链接失效、网络错误),当前代码不会返回响应,导致浏览器无限等待。添加错误处理:
request(link, function(error, response, html) { if (error) { console.log("获取文章详情失败:", error); return res.status(500).send("无法加载文章内容"); } // 后续Cheerio处理逻辑 });
- 修复/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
相关产品推荐
相关产品推荐

