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

Node.js多域名爬虫:Axios异步结果无法全局传递至Hbs渲染

解决Axios异步数据无法全局访问及多网站爬取的问题

问题根源

你遇到的空数组问题本质是JavaScript异步执行机制导致的:Axios发起的HTTP请求是异步操作,then回调内的代码要等请求完成后才会执行,但全局作用域的console.log(finalArray)和Express路由代码是同步执行的——这时候请求还没返回,finalArray根本没被赋值,自然是空的。

如果把路由代码塞进单个then回调,确实能拿到数据,但这种写法没法扩展到多网站爬取,因为每个网站的请求then都是独立的,没法统一合并结果。

解决方案:用Async/Await封装爬取逻辑,路由内统一处理多请求

核心思路是:

  1. 把单个网站的爬取逻辑封装成异步函数,方便复用。
  2. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 00:40:42