You need to enable JavaScript to run this app. Puppeteer爬取CoinMarketCap数据跳行、元素不全的原因及优化
优惠活动
大模型
产品
解决方案
定价
更多

Puppeteer爬取CoinMarketCap数据跳行、元素不全的原因及优化

随机跳过表格行的核心原因
  • 站点采用虚拟滚动渲染机制:CoinMarketCap的行情表不会一次性将100行数据全部挂载到DOM中,仅渲染当前视口范围内的表格行,视口外的行会被临时回收卸载。你在点击排序后立刻执行数据提取时,只有当前已渲染的行能被查询到,未挂载的行直接被漏掉,因为渲染进度受网络速度、屏幕尺寸影响,每次漏行的位置都是随机的。
  • 等待逻辑存在缺陷:现有代码仅等待第一行元素渲染完成,无法确认排序完成后全量表格行是否加载结束;同时提取逻辑存在bug:ticker和percentage变量直接持有DOM元素引用,没有读取innerText值,Puppeteer的page.evaluate无法序列化DOM对象,实际返回的这两个字段值为undefined。
  • 定位选择器存在偏差:提取涨跌幅字段时,选择器直接从全局.cmc-table > tbody下按行号查询,而提取名称、代码时没有限定在同一个表格上下文,很容易匹配到页面其他嵌套表格的同名元素,导致元素存在性判断失效,触发跳行逻辑。
  • 选择器绑定动态生成类名:代码中使用的sc-16r8icm-0这类类名是站点CSS-in-JS打包时随机生成的,会随站点版本更新发生变化,进一步加剧元素定位失配的概率。
更优的实现方案

核心调整方向

  • 避开易失的随机类名选择器,优先使用站点公开维护的稳定语义类名、文本属性定位元素
  • 适配虚拟滚动机制:排序完成后主动逐次滚动页面,触发表格全量加载,等待DOM中挂载的目标行数达到预期后再执行提取
  • 调整遍历逻辑:先获取当前表格下所有行的DOM列表,逐行在自身上下文中提取字段,不再靠全局行号反查元素,避免DOM重排导致的行序错位
  • 严格按需求格式提取字段:只取名称、ticker、24小时涨跌幅三个字段,剔除冗余的id等额外字段
const puppeteer = require('puppeteer')

async function getTopCryptoData() {
  const browser = await puppeteer.launch({ args: ['--no-sandbox'], headless: true })
  const page = await browser.newPage()
  await page.setViewport({ width: 1536, height: 1080 })

  await page.goto('https://coinmarketcap.com/', { waitUntil: 'networkidle2' })

  // 等待24h涨跌幅列的排序按钮出现并触发排序
  const sortBtn = await page.waitForSelector('th:contains("24h %")')
  await sortBtn.click()
  // 等待排序后的第一行数据渲染完成
  await page.waitForSelector('tbody tr:first-child')

  // 触发虚拟滚动全量加载100行表格
  await page.evaluate(async () => {
    const tableBody = document.querySelector('tbody')
    let lastRowCount = 0
    // 循环滚动直到加载满100行,或行数不再增长
    while (tableBody.querySelectorAll('tr').length < 100) {
      window.scrollBy(0, 500)
      // 等待200ms让浏览器完成新行加载
      await new Promise(resolve => setTimeout(resolve, 200))
      const currentCount = tableBody.querySelectorAll('tr').length
      if (currentCount === lastRowCount) break
      lastRowCount = currentCount
    }
  })

  // 等待表格行数稳定到目标值
  await page.waitForFunction(() => document.querySelectorAll('tbody tr').length >= 100, { timeout: 10000 })

  const cryptoData = await page.evaluate(() => {
    const rows = document.querySelectorAll('tbody tr')
    const tempData = []
    rows.forEach(row => {
      // 逐行在当前行的上下文内查找元素,避免跨表匹配
      const nameEl = row.querySelector('.cmc-table__cell--sort-by__name')
      const tickerEl = row.querySelector('.cmc-table__cell--sort-by__name + td .coin-item-symbol')
      const percentEl = row.querySelector('.cmc-table__cell--sort-by__percent-change-24-h')
      
      if (nameEl && tickerEl && percentEl) {
        tempData.push({
          name: nameEl.innerText.trim(),
          ticker: tickerEl.innerText.trim(),
          percentage: percentEl.innerText.trim()
        })
      }
    })
    return tempData
  })

  console.log(cryptoData)
  await browser.close()
  return cryptoData
}

getTopCryptoData()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.05 01:06:03