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

Puppeteer爬取YouTube缩略图URL出现异常空行,标题文本爬取正常

问题根源定位
  • 出现连续空行的核心原因不是滚动逻辑的空跳转,而是YouTube 虚拟滚动机制会清空滑出视口的旧缩略图元素的 src 属性:
    为了降低内存占用,YouTube 首页的虚拟滚动实现会把视口外的缩略图 src 置为空字符串或占位的 base64 透明图,用 el.src 读取时会得到空值。标题元素的 textContent 不会被动态清空,所以标题输出完全正常。
  • 代码里的其他加重问题的逻辑:
    • 仅在页面加载时滚动了1次,后续50次循环没有触发新内容加载,每次抓取的都是全页面的所有匹配元素,包括已被清空 src 的旧元素。
    • $$eval 后直接对全量数组做 join('\n'),没有过滤掉空 src 项,空数组项直接转成了空行。
    • 没有做去重处理,旧元素的重复输出会放大空行的循环特征。
修正方案
  • 每次循环先触发滚动加载新内容,匹配 YouTube 的无限滚动逻辑
  • 过滤无效的空 src、非媒体资源的 URL,同时按视频唯一标识去重,避免重复抓取
  • 优先从 img 元素的 data-src 或上级节点的静态属性取缩略图地址,避免被虚拟滚动清空的 src 影响
  • 不要直接把抓取后的数组 join 成字符串再输出,先完成过滤处理再拼接
修正后可运行的代码
async function scrape(url) {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();
  await page.setViewport({ width: 1200, height: 800 });

  await page.goto(url, { timeout: 0, waitUntil: 'domcontentloaded' });

  const collectedData = new Map(); // 用Map去重,key为视频链接,保证条目唯一
  // 模拟滚动加载,直到收集到足够数量的视频
  while (collectedData.size < 50) {
    // 触发滚动加载下一批内容
    await page.evaluate(() => window.scrollBy(0, window.innerHeight * 2));
    await page.waitForTimeout(1500); // 等待新内容渲染完成

    // 抓取当前已渲染的视频项,在浏览器上下文完成无效内容过滤
    const batchData = await page.evaluate(() => {
      const videoItems = document.querySelectorAll('ytd-rich-item-renderer');
      const res = [];
      videoItems.forEach(item => {
        const titleEl = item.querySelector('h3 > a > #video-title');
        const imgEl = item.querySelector('ytd-thumbnail > a > yt-img-shadow > #img');
        // 跳过无效元素
        if (!titleEl || !imgEl || !titleEl.textContent.trim()) return;
        // 优先取静态绑定的data-src地址,取不到再降级用src,过滤空值
        let src = imgEl.getAttribute('data-src') || imgEl.src;
        if (!src || !src.startsWith('https://')) return;
        res.push({
          title: titleEl.textContent.trim(),
          src: src,
          videoHref: titleEl.href
        })
      })
      return res;
    })

    // 批量去重后加入总集合
    batchData.forEach(item => {
      if (!collectedData.has(item.videoHref)) {
        collectedData.set(item.videoHref, item);
      }
    })
  }

  // 输出处理完成的无空行内容
  const allText = Array.from(collectedData.values()).map(i => i.title).join('\n');
  const allSrc = Array.from(collectedData.values()).map(i => i.src).join('\n');
  console.log('标题列表:\n', allText);
  console.log('缩略图URL列表:\n', allSrc);

  await browser.close();
}
关键逻辑说明
  • 用 Map 按视频链接去重,避免同一视频重复输出
  • 直接在浏览器上下文完成元素过滤、空值校验,减少无效数据传到 Node 端
  • 优先读取 data-src 属性,绕开虚拟滚动对动态 src 的清空逻辑
  • 每轮滚动后预留等待时间,避免抓取未渲染完成的空元素

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.23 13:24:03