Node.js多域名爬虫:Axios异步结果无法全局传递至Hbs渲染
解决Axios异步数据无法全局访问及多网站爬取的问题
问题根源
你遇到的空数组问题本质是JavaScript异步执行机制导致的:Axios发起的HTTP请求是异步操作,then回调内的代码要等请求完成后才会执行,但全局作用域的console.log(finalArray)和Express路由代码是同步执行的——这时候请求还没返回,finalArray根本没被赋值,自然是空的。
如果把路由代码塞进单个then回调,确实能拿到数据,但这种写法没法扩展到多网站爬取,因为每个网站的请求then都是独立的,没法统一合并结果。
解决方案:用Async/Await封装爬取逻辑,路由内统一处理多请求
核心思路是:
- 把单个网站的爬取逻辑封装成异步函数,方便复用。
- 在Express路由处理函数中,用
Promise.all并行发起多个网站的爬取请求,等所有请求完成后合并结果,再渲染页面。
完整代码示例
const axios = require('axios') const cheerio = require('cheerio') const express = require('express') const hbs = require('hbs') const app = express() // 配置hbs模板引擎,默认读取views文件夹下的模板文件 app.set('view engine', 'hbs') // 要爬取的目标网站列表 const targetUrls = [ 'https://www.google.com/', 'https://example.com/', // 可以继续添加更多网站 ] // 封装单个网站的爬取函数 async function scrapeSingleSite(url) { try { const response = await axios(url) const html = response.data const $ = cheerio.load(html) const siteArticles = [] // 提取目标数据(保留你的选择器逻辑) $('.sprite', html).parent().children('a').each(function() { const text = $(this).attr('title') // 过滤空值,避免无效数据 if (text) { siteArticles.push(text) } }) return siteArticles } catch (err) { console.error(`爬取${url}失败:`, err) // 爬取失败返回空数组,不影响其他网站的结果 return [] } } // 首页路由:爬取所有网站数据后渲染页面 app.get('/', async (req, res) => { try { // 并行爬取所有目标网站,提高效率 const allSiteResults = await Promise.all(targetUrls.map(url => scrapeSingleSite(url))) // 合并所有网站的结果为一个数组 const finalArray = allSiteResults.flat() // 渲染home.hbs模板,传递数据 res.render('home', { array: finalArray }) } catch (err) { console.error('整体爬取流程出错:', err) // 出错时返回空数组和错误提示 res.render('home', { array: [], error: '数据加载失败,请稍后重试' }) } }) // 启动服务器 app.listen(3000, () => { console.log('服务器已启动:http://localhost:3000') })
可选优化:缓存数据避免重复爬取
如果不想每次用户访问都爬取一次网站(减少请求频率,避免被反爬),可以添加缓存逻辑,定时刷新数据:
// 缓存全局变量 let cachedFinalArray = [] // 初始化时爬取一次数据 async function initData() { const allSiteResults = await Promise.all(targetUrls.map(url => scrapeSingleSite(url))) cachedFinalArray = allSiteResults.flat() } initData() // 定时刷新数据,比如每1小时刷新一次(3600000毫秒) setInterval(initData, 3600000) // 修改路由,直接使用缓存数据 app.get('/', (req, res) => { res.render('home', { array: cachedFinalArray }) })
注意事项
- 模板路径:
res.render('home')会自动读取views/home.hbs,不需要写views/home,如果你的模板位置特殊,需要额外配置app.set('views', './自定义模板路径')。 - 反爬风险:频繁爬取网站可能触发反爬机制,建议添加请求间隔、设置User-Agent等。
- 错误处理:每个爬取请求都单独做了错误捕获,避免单个网站爬取失败导致整个页面无法渲染。
内容的提问来源于stack exchange,提问作者rezeroo
相关产品推荐
相关产品推荐

