Puppeteer爬取YouTube缩略图URL出现异常空行,标题文本爬取正常
问题根源定位
- 出现连续空行的核心原因不是滚动逻辑的空跳转,而是YouTube 虚拟滚动机制会清空滑出视口的旧缩略图元素的
src属性:
为了降低内存占用,YouTube 首页的虚拟滚动实现会把视口外的缩略图src置为空字符串或占位的 base64 透明图,用el.src读取时会得到空值。标题元素的textContent不会被动态清空,所以标题输出完全正常。 - 代码里的其他加重问题的逻辑:
- 仅在页面加载时滚动了1次,后续50次循环没有触发新内容加载,每次抓取的都是全页面的所有匹配元素,包括已被清空
src的旧元素。 $$eval后直接对全量数组做join('\n'),没有过滤掉空src项,空数组项直接转成了空行。- 没有做去重处理,旧元素的重复输出会放大空行的循环特征。
- 仅在页面加载时滚动了1次,后续50次循环没有触发新内容加载,每次抓取的都是全页面的所有匹配元素,包括已被清空
修正方案
- 每次循环先触发滚动加载新内容,匹配 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
相关产品推荐
相关产品推荐

