使用Puppeteer爬取YouTube直播SuperChat失效如何解决
问题原因
现有代码无法正常抓取YouTube SuperChat,核心是三个和YouTube页面逻辑相关的问题:
page.goto()默认仅等待页面load事件触发,YouTube直播聊天区是独立懒加载模块,load事件触发时聊天相关DOM节点还未渲染,查询操作自然拿不到结果- 你使用的
#purchase-amount-chip选择器仅在SuperChat悬浮高亮的短暂时效内存在,不是SuperChat内容的常驻容器选择器,且Super Thanks、付费表情包的DOM结构和普通SuperChat完全分离 - Puppeteer默认启动参数携带的自动化特征会被YouTube反爬机制识别,直接返回不带实时聊天模块的降级页面
可直接运行的修复版本
代码做了三处核心调整:启动时加反检测参数绕过YouTube自动化识别、等待聊天模块完全加载后再执行操作、通过DOM监听器持续捕获实时新增的SuperChat/付费表情包(后续扩展Super Thanks只需补充对应选择器即可),代码会自动把抓到的内容追加写入本地文件:
const puppeteer = require('puppeteer') const fs = require("fs/promises") async function start() { const browser = await puppeteer.launch({ headless: "new", args: [ '--disable-blink-features=AutomationControlled', '--start-maximized' ], defaultViewport: null }) const page = await browser.newPage() // 注入脚本绕过webdriver特征检测 await page.evaluateOnNewDocument(() => { Object.defineProperty(navigator, 'webdriver', {get: () => undefined}) }) // 等待网络请求空闲,确保聊天模块资源加载完成 await page.goto("https://www.youtube.com/watch?v=1ykh6t3mec0", { waitUntil: 'networkidle2', timeout: 60000 }) // 显式等待聊天列表容器渲染完成 await page.waitForSelector('#chat #items', {timeout: 30000}) // 注册写入文件的方法,供页面上下文调用 await page.exposeFunction('appendRecord', async (content) => { await fs.appendFile("superchats.txt", content + '\r\n') }) await page.evaluate(() => { const chatContainer = document.querySelector('#chat #items') // SuperChat消息选择器 const SUPER_CHAT_SELECTOR = 'yt-live-chat-paid-message-renderer' // 付费表情包(Super Stickers)选择器 const SUPER_STICKER_SELECTOR = 'yt-live-chat-paid-sticker-renderer' // 解析单条付费内容并写入文件 const parsePaidContent = (node) => { if (node.nodeType !== 1) return const timeStr = new Date().toLocaleString() // 解析普通SuperChat if (node.matches(SUPER_CHAT_SELECTOR)) { const user = node.querySelector('#author-name')?.textContent?.trim() || '未知用户' const amount = node.querySelector('#purchase-amount')?.textContent?.trim() || '未知金额' const msg = node.querySelector('#message')?.textContent?.trim() || '无文本内容' window.appendRecord(`[SuperChat ${timeStr}] 用户:${user} 金额:${amount} 内容:${msg}`) } // 解析付费表情包 if (node.matches(SUPER_STICKER_SELECTOR)) { const user = node.querySelector('#author-name')?.textContent?.trim() || '未知用户' const amount = node.querySelector('#purchase-amount')?.textContent?.trim() || '未知金额' const stickerDesc = node.querySelector('#img')?.alt || '未知表情包' window.appendRecord(`[SuperSticker ${timeStr}] 用户:${user} 金额:${amount} 表情包:${stickerDesc}`) } } // 先抓取页面加载完成时已经存在的存量付费内容 chatContainer.querySelectorAll(`${SUPER_CHAT_SELECTOR}, ${SUPER_STICKER_SELECTOR}`).forEach(parsePaidContent) // 注册DOM监听器,实时捕获后续新增的付费内容 const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(parsePaidContent) }) }) observer.observe(chatContainer, {childList: true, subtree: true}) }) // *不要提前调用browser.close()*,保持浏览器运行持续抓取,不需要抓取时再手动关闭 } start()
运行注意事项
- 首次运行如果弹出YouTube Cookie同意弹窗,手动点击同意一次即可,后续启动会复用本地用户数据不需要重复操作
- 如果进入直播间时聊天区是默认折叠状态,需要在代码里补充自动点击「显示聊天」按钮的逻辑,否则聊天容器不会渲染
- 长时间运行建议加随机的行为模拟,不要一直挂着无操作,避免触发YouTube的反爬限制导致IP被临时封禁
- 后续扩展Super Thanks支持时,只需要补充对应Super Thanks的DOM选择器,在
parsePaidContent方法里加对应的解析逻辑即可
内容的提问来源于stack exchange,提问作者JoeG
相关产品推荐
相关产品推荐

