使用Node.js和Cheerio抓取商场店铺名称失败问题排查
看起来你遇到的问题很典型——目标页面是客户端渲染的单页应用(SPA),这也是为什么你的request+cheerio组合在其他静态网站能用,但在这里失效了。
核心原因分析
你用request获取到的只是页面的初始静态HTML,而页面里的店铺列表是通过JavaScript动态加载并渲染到DOM中的。当cheerio解析这份静态HTML时,目标的h2.card-secondary-title.name.copy元素还不存在(你可以尝试在代码里console.log(body),会发现#root容器里几乎是空的,没有你看到的店铺DOM结构)。你浏览器里看到的完整DOM,是浏览器执行了页面JS之后才生成的。
解决办法
针对这类SPA页面,有两种常用的抓取方案:
1. 使用支持JavaScript渲染的爬虫工具
比如puppeteer(Chrome官方无头浏览器工具)或者playwright,它们会模拟真实浏览器加载页面,等待JS执行完成后再获取渲染后的DOM。
示例代码(用Puppeteer):
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('http://www.simon.com/mall/anchorage-5th-avenue-mall/stores', { waitUntil: 'networkidle2' }); // 等待网络请求稳定 // 提取店铺名称 const storeNames = await page.evaluate(() => { const elements = document.querySelectorAll('h2.card-secondary-title.name.copy'); return Array.from(elements).map(el => el.textContent.trim()); }); console.log(storeNames); await browser.close(); })();
2. 直接抓取后端API接口(更高效)
打开浏览器的开发者工具(F12),切换到Network面板,刷新页面后筛选XHR/Fetch请求,找到加载店铺数据的API接口(通常是返回JSON格式的请求)。直接请求这个接口获取数据,比渲染整个页面效率高得多。
比如你可能会找到类似/api/stores/mall/anchorage-5th-avenue这类的接口,用request或者axios直接请求它就能拿到结构化的店铺数据,不需要解析HTML。
快速验证方法
在你的现有代码里先打印body,确认原始HTML是否包含目标元素:
request(url, function(err, resp, body) { if (err) { console.log(err); } else { console.log(body); // 查看静态HTML里是否有店铺相关DOM var $ = cheerio.load(body); $('h2.card-secondary-title.name.copy').each(function() { var text = $(this).text(); console.log(text); }); } })
如果打印的HTML里没有那些h2元素,就坐实了是客户端渲染的问题。
内容的提问来源于stack exchange,提问作者WhiteDillPickle

