获取 YouTube 自创 Clips: API 不行? 探索替代方案
2025-04-24 11:11:51
好的,这是你要的博客文章内容:
如何获取 YouTube 频道上自己创建的 Clips?(API 限制与替代方案)
遇到的问题:获取 YouTube 上我创建的 Clips
不少 YouTube 内容创作者或者开发者都想通过程序化的方式,获取自己在频道上创建的 Clips 列表。具体点说,就是想拿到 https://www.youtube.com/feed/clips
这个页面展示的数据。
自然而然,大家会想到用官方的 YouTube Data API v3 。但试了一圈,发现 API 文档里好像没有直接提供获取“已创建 Clips”的功能。
有人就尝试了“曲线救国”的路线,比如用 Node.js 配合 Puppeteer 这类浏览器自动化工具,模拟真人去访问那个 feed/clips
页面,然后解析页面 HTML 来提取 Clips 信息。就像问题里提到的那样,直接在浏览器控制台运行 JavaScript 也能抓取当前加载出来的数据。
不过,这条路也不好走。用 Puppeteer 会遇到麻烦:
- Google 账号登录复杂 :自动化登录 Google 账号不是件容易事,经常会碰到验证码、二次验证 (2FA) 或者一些人机验证挑战,很容易失败。
- 页面加载判断困难 :
feed/clips
页面是动态加载的,往下滚动才会出现更多 Clips。用 Puppeteer 需要精确地等待页面元素加载完成,或者模拟滚动到底部,这增加了脚本的复杂度和不稳定性。
那么,用 API 到底行不行?如果不行,还有没有其他靠谱的方法?
API 现状分析:为啥 YouTube Data API v3 不管用?
直接说结论:目前的 YouTube Data API v3 确实没有提供直接获取用户 创建 的 Clips 列表的功能。
我们翻阅 YouTube Data API v3 的官方文档,可以看到它提供了很多功能,比如:
- 获取视频信息 (
Videos
) - 管理播放列表 (
Playlists
,PlaylistItems
) - 搜索内容 (
Search
) - 获取频道信息 (
Channels
) - 查看用户活动,比如上传、点赞 (
Activities
)
但里面唯独没有一个叫做 Clips
的资源类型,也没有哪个现有接口明确说明可以返回“由我创建的 Clips”。Clips 相对较新,且功能定位更像是对现有视频的一种“标注”或“快捷方式”,可能 Google 还没有把它完全整合到现有的 Data API 体系里。
用户活动 (Activities
) 接口听起来有点像,但它通常记录的是上传视频、添加到播放列表、点赞这类事件,并没有包含“创建了一个 Clip”这样的活动类型,更别说返回详细的 Clips 列表了。
所以,想通过官方推荐的标准 API 途径来解决这个问题,目前看是行不通的。
探索替代方案:API 不行,那咋办?
既然 API 这条路走不通,我们就得考虑其他方法了。
方案一:硬着头皮上抓取 (Puppeteer 等工具)
虽然用 Puppeteer 有困难,但它仍然是理论上可行的一种自动化方案。如果非要走这条路,需要克服几个关键点:
1. 处理登录验证
这是最大的拦路虎。
-
原理 : 模拟用户在浏览器中输入账号密码登录 Google。
-
操作 :
- 使用 Puppeteer 打开 Google 登录页面。
- 定位用户名、密码输入框,填入信息。
- 处理可能出现的各种验证:点击“下一步”、选择账户、输入二次验证码、解决 reCAPTCHA 等。这部分逻辑非常复杂,且 Google 的登录流程随时可能变化。
- 手动维护 Cookie/Session : 一种稍微简单点的方法是,先手动在某个浏览器登录 Google,然后把有效的 Cookie 复制出来,让 Puppeteer 在请求时带上这些 Cookie。这种方式避免了模拟登录,但 Cookie 会过期,需要定期手动更新,自动化程度打了折扣。
-
代码示例 (概念性 - 手动维护 Cookie) :
const puppeteer = require('puppeteer'); async function getClipsWithCookies(cookies) { const browser = await puppeteer.launch(); const page = await browser.newPage(); // 设置从浏览器获取的 Cookies await page.setCookie(...cookies); // cookies 需要是正确的格式 try { // 直接访问目标页面,因为已经带了 Cookie,理论上是登录状态 await page.goto('https://www.youtube.com/feed/clips', { waitUntil: 'networkidle2' }); // 等待 Clips 列表容器加载出来 await page.waitForSelector('#contents ytd-rich-item-renderer', { timeout: 10000 }); // 选择器可能变化,需要检查确认 // --- 滚动加载更多内容 --- // 这部分逻辑比较复杂,可能需要多次滚动并等待新内容加载 let previousHeight; while (true) { previousHeight = await page.evaluate('document.documentElement.scrollHeight'); await page.evaluate('window.scrollTo(0, document.documentElement.scrollHeight)'); // 等待新内容加载或者滚动到底部(需要合适的等待策略) try { await page.waitForFunction(`document.documentElement.scrollHeight > ${previousHeight}`, { timeout: 5000 }); } catch (e) { // 超时可能意味着滚动到底部或者加载出错 console.log('Scroll timeout or reached bottom.'); break; } await page.waitForTimeout(1000); // 等待一小段时间让内容渲染 } // --- 滚动结束 --- // 执行页面解析逻辑(类似问题中提供的代码) const clips = await page.evaluate(() => { const listClipsHTML = document.querySelectorAll("#contents #dismissible"); // 选择器可能需要根据实际情况调整 const clipsData = []; listClipsHTML.forEach(clipHTML => { try { const clip = {}; clip.img = clipHTML.querySelector("yt-image img")?.src; clip.link = clipHTML.querySelector("a#thumbnail")?.href; // 注意:选择器可能非常不稳定,YouTube 更新就会失效 clip.length = clipHTML.querySelector("#time-status #text")?.innerText.trim(); clip.viewed = clipHTML.querySelector("#metadata-line span:nth-child(1)")?.innerHTML.trim(); // 这里是 innerHTML clip.title = clipHTML.querySelector("#video-title")?.innerText.trim(); // 用 innerText 获取文本 clip.author = clipHTML.querySelector("#channel-name #text")?.title; // 作者信息可能在这里 clip.createdAt = clipHTML.querySelector("#metadata-line span:nth-child(2)")?.innerHTML.trim(); // 这里是 innerHTML if (clip.link && clip.title) { // 确保核心数据存在 clipsData.push(clip); } } catch (error) { console.error("Error parsing one clip element:", error); // 选择性地记录错误,或者跳过这个元素 } }); return clipsData; }); console.log(`成功获取到 ${clips.length} 个 Clips:`); console.log(clips); } catch (error) { console.error('抓取 Clips 失败:', error); } finally { await browser.close(); } } // --- 如何获取 Cookies --- // 1. 手动登录你的 Google/YouTube 账号。 // 2. 打开开发者工具 (F12)。 // 3. 找到 Network 或 Application -> Cookies -> youtube.com。 // 4. 复制关键的 Cookie 值(比如 HSID, SSID, SID, LOGIN_INFO 等),构造成 Puppeteer 需要的格式。 // 这个过程比较繁琐,且 Cookie 有时效性。 const exampleCookies = [ // 这里需要填入你手动获取的、符合格式的 Cookie 对象数组 // { name: 'LOGIN_INFO', value: 'xxx', domain: '.youtube.com', ... }, // { name: 'SID', value: 'xxx', domain: '.google.com', ... }, // ... 其他必要的 Cookies ]; if (exampleCookies.length > 0) { getClipsWithCookies(exampleCookies); } else { console.warn("请先手动配置有效的 Cookies!"); }
2. 处理动态内容加载
- 原理 :
feed/clips
页面不是一次性加载所有 Clips 的,需要向下滚动页面,YouTube 前端脚本才会触发加载更多的 Clips。 - 操作 :
- 使用 Puppeteer 的
page.evaluate()
方法执行 JavaScript 来模拟滚动。 - 滚动后,需要等待新的 Clips 元素出现在 DOM 中 (
page.waitForSelector
或page.waitForFunction
)。 - 重复“滚动 - 等待”这个过程,直到没有新的 Clips 加载出来(比如滚动到底部,或者连续几次滚动后页面高度不再增加)。
- 使用 Puppeteer 的
- 挑战 : 等待策略很难完美。是等待固定时间?还是等待特定元素?或是判断页面高度变化?都需要仔细调试,并且 YouTube 前端一改版就可能失效。上面代码示例中包含了一个简单的滚动逻辑。
3. 解析 HTML 数据
- 原理 : 使用 DOM 选择器 (CSS Selector) 来定位包含 Clip 信息的 HTML 元素,提取标题、链接、缩略图等数据。
- 操作 : 问题中提供的 JavaScript 代码片段就是一个很好的起点。在 Puppeteer 中,可以使用
page.evaluate()
或page.$$eval()
来执行类似的 DOM 操作。 - 挑战 : CSS 选择器非常脆弱。YouTube 前端代码结构的任何微小调整,都可能导致选择器失效,抓取脚本崩溃。需要频繁检查和更新选择器。
安全建议与风险 :
- 账号安全 : 自动化登录,特别是绕过人机验证,可能违反 Google 的服务条款,有导致账号被暂时限制甚至封禁的风险。使用手动维护 Cookie 的方式相对风险小一点,但还是存在被检测到的可能。
- 稳定性差 : 强依赖于前端页面结构,YouTube 一更新界面,脚本就得跟着改,维护成本很高。
- 性能开销 : 启动一个完整的浏览器实例 (Puppeteer/Selenium) 资源消耗较大。
总的来说,抓取方案能跑通,但用起来会比较“心累”,只适合小范围、非关键任务或者临时用用。
方案二:关注官方渠道和反馈
这是一个更稳妥、更长远的思路,虽然不能马上解决问题。
- 原理 : 向 Google/YouTube 反馈你的需求,希望他们在未来的 API 版本中增加获取 Clips 的功能。
- 操作 :
- 关注 YouTube Data API 的官方文档和博客 : 看有没有关于 Clips 的更新计划。
- 在 Google Issue Tracker 上搜索 : 看看是否已经有人提交了类似的功能请求 (Feature Request)。如果有了,可以去“+1”或者添加你的使用场景,增加需求的可见度。
- 提交新的 Feature Request : 如果没有找到相关的请求,可以考虑自己提交一个,详细说明为什么需要这个功能、你的具体使用场景是什么。
虽然这需要等待,但一旦官方 API 支持了,那将是最稳定、最可靠的解决方案。
方案三:分析网络请求 (高级,不推荐用于生产环境)
这是一种更“黑科技”的方法,风险和不稳定性甚至高于页面抓取。
-
原理 : 当你在浏览器里访问
https://www.youtube.com/feed/clips
时,浏览器背后会向 YouTube 的服务器发送一些请求(通常是 XHR 或 Fetch 请求)来获取数据。这些请求可能使用了未公开的内部 API。 -
操作 :
- 打开浏览器的开发者工具 (F12),切换到“网络”(Network) 面板。
- 访问
https://www.youtube.com/feed/clips
页面,并可能需要滚动页面加载更多内容。 - 在 Network 面板中筛选 XHR/Fetch 类型的请求,查找那些响应 (Response) 内容包含 Clips 数据的请求(通常是 JSON 格式)。
- 分析这些请求的 URL、请求头 (Headers,尤其是
Authorization
,Cookie
等认证信息)、请求参数。 - 尝试用代码(比如 Node.js 的
axios
或 Python 的requests
)模拟发送这些请求。
-
代码示例 (概念性 - 使用 axios)
const axios = require('axios'); async function fetchClipsViaInternalAPI(authToken, cookieHeader, otherHeaders) { // 警告:这里的 URL, 参数, Headers 都是假设的,需要自行抓包分析得出 const apiUrl = 'https://www.youtube.com/youtubei/v1/browse?key=AIzaSy...&prettyPrint=false'; // 示例 URL,绝对会变 const requestPayload = { // 请求体也需要抓包分析 context: { /* ... 大量上下文信息 ... */ }, continuation: "..." // 用于分页加载的 token }; try { const response = await axios.post(apiUrl, requestPayload, { headers: { 'Authorization': `Bearer ${authToken}`, // 可能需要 Bearer Token 'Cookie': cookieHeader, // 或者需要 Cookie 'X-Goog-Api-Key': '...', // 可能需要 API Key 'Content-Type': 'application/json', ...otherHeaders // 其他必要的头部信息 } }); // 解析返回的 JSON 数据,提取 Clips 信息 // JSON 结构非常复杂且 undocumented,需要仔细研究 const clips = parseClipsFromApiResponse(response.data); console.log('通过内部 API 获取 Clips 成功:', clips); return clips; } catch (error) { console.error('调用内部 API 失败:', error.response?.data || error.message); return null; } } function parseClipsFromApiResponse(data) { // 解析逻辑极其复杂,依赖于抓包得到的具体 JSON 结构 // ... return []; // 返回解析后的 Clips 数组 } // 你需要从浏览器开发者工具中获取真实的认证信息和 Headers const userAuthToken = '...'; // 可能没有,或者形式不同 const userCookieHeader = 'SID=...; HSID=...; ...'; // 从 Network 请求中复制 const necessaryHeaders = { /* ... */ }; // fetchClipsViaInternalAPI(userAuthToken, userCookieHeader, necessaryHeaders); console.warn("内部 API 分析非常复杂且不稳定,不推荐在生产环境中使用!");
-
安全建议与风险 :
- 极度不稳定 : 这些内部 API 是 YouTube 自己前端用的,没有任何文档,随时可能更改、限制甚至移除,不提供任何兼容性保证。依赖它的代码极易失效。
- 认证复杂 : 通常需要复杂的认证 Cookie 或 Token,获取和维护这些认证信息很困难。
- 可能违反 ToS : 调用未公开的 API 也可能被视为违反服务条款。
这个方法只适合有很强逆向工程能力、并且能接受高维护成本和高风险的开发者进行探索性尝试。强烈不建议在需要稳定性的项目中使用。
总结与展望:所以,到底能不能行?
目前来看,想要稳定、可靠地通过程序获取你自己创建的 YouTube Clips 列表,还没有一个官方的、推荐的方式。
- YouTube Data API v3 :不支持此功能。
- 页面抓取 (Puppeteer) :技术上可行,但登录验证和动态内容处理是难点,且脚本脆弱、维护成本高、有账号风险。
- 分析内部网络请求 :技术门槛高,极不稳定,不适合大多数场景。
最务实的做法可能是:
- 短期 : 如果需求不迫切或者量不大,先手动处理。如果必须自动化,并且能接受风险和维护成本,可以尝试 带有手动维护 Cookie 的页面抓取方案 ,这是相对不那么痛苦的抓取方式。
- 长期 : 关注 YouTube Data API 的更新,并在官方渠道反馈你的需求。期待 Google 未来能提供官方支持。