返回

获取 YouTube 自创 Clips: API 不行? 探索替代方案

javascript

好的,这是你要的博客文章内容:

如何获取 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 会遇到麻烦:

  1. Google 账号登录复杂 :自动化登录 Google 账号不是件容易事,经常会碰到验证码、二次验证 (2FA) 或者一些人机验证挑战,很容易失败。
  2. 页面加载判断困难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.waitForSelectorpage.waitForFunction)。
    • 重复“滚动 - 等待”这个过程,直到没有新的 Clips 加载出来(比如滚动到底部,或者连续几次滚动后页面高度不再增加)。
  • 挑战 : 等待策略很难完美。是等待固定时间?还是等待特定元素?或是判断页面高度变化?都需要仔细调试,并且 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 的功能。
  • 操作 :
    1. 关注 YouTube Data API 的官方文档和博客 : 看有没有关于 Clips 的更新计划。
    2. 在 Google Issue Tracker 上搜索 : 看看是否已经有人提交了类似的功能请求 (Feature Request)。如果有了,可以去“+1”或者添加你的使用场景,增加需求的可见度。
    3. 提交新的 Feature Request : 如果没有找到相关的请求,可以考虑自己提交一个,详细说明为什么需要这个功能、你的具体使用场景是什么。

虽然这需要等待,但一旦官方 API 支持了,那将是最稳定、最可靠的解决方案。

方案三:分析网络请求 (高级,不推荐用于生产环境)

这是一种更“黑科技”的方法,风险和不稳定性甚至高于页面抓取。

  • 原理 : 当你在浏览器里访问 https://www.youtube.com/feed/clips 时,浏览器背后会向 YouTube 的服务器发送一些请求(通常是 XHR 或 Fetch 请求)来获取数据。这些请求可能使用了未公开的内部 API。

  • 操作 :

    1. 打开浏览器的开发者工具 (F12),切换到“网络”(Network) 面板。
    2. 访问 https://www.youtube.com/feed/clips 页面,并可能需要滚动页面加载更多内容。
    3. 在 Network 面板中筛选 XHR/Fetch 类型的请求,查找那些响应 (Response) 内容包含 Clips 数据的请求(通常是 JSON 格式)。
    4. 分析这些请求的 URL、请求头 (Headers,尤其是 Authorization, Cookie 等认证信息)、请求参数。
    5. 尝试用代码(比如 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) :技术上可行,但登录验证和动态内容处理是难点,且脚本脆弱、维护成本高、有账号风险。
  • 分析内部网络请求 :技术门槛高,极不稳定,不适合大多数场景。

最务实的做法可能是:

  1. 短期 : 如果需求不迫切或者量不大,先手动处理。如果必须自动化,并且能接受风险和维护成本,可以尝试 带有手动维护 Cookie 的页面抓取方案 ,这是相对不那么痛苦的抓取方式。
  2. 长期 : 关注 YouTube Data API 的更新,并在官方渠道反馈你的需求。期待 Google 未来能提供官方支持。