我可以使用 Youtube API 在字幕轨道中搜索关键字/时间戳吗?

Posted

技术标签:

【中文标题】我可以使用 Youtube API 在字幕轨道中搜索关键字/时间戳吗?【英文标题】:Can I use the Youtube API to search for keywords/timestamps in the caption track? 【发布时间】:2021-10-19 12:25:31 【问题描述】:

我想创建一个 chrome 扩展程序,让您可以搜索任何 youtube 视频的字幕轨道,以查找出现所需字符串的任何实例。 基本上 CMD F 适用于任何视频。键入您要查找的单词/短语,扩展程序将为该单词/短语的每次出现返回一个时间戳

我对 Youtube API 的研究越多,除非我是视频的所有者,否则它看起来是不可能的。如果是这样,有什么想法可以解决这个问题吗? 可以在视频上手动打开脚本(带有时间戳)并观看它与视频一起播放。

只是觉得这听起来像是一个有用且具有挑战性的项目,可以帮助我学习 Web 开发。

【问题讨论】:

【参考方案1】:

欢迎卡莱布。如果您无法通过 API 访问数据,则只能尝试通过内容脚本访问数据。由于网页的 javascript 都没有暴露给内容脚本(有充分的理由),因此您只能访问/操作 DOM,并监听事件。

我将尽力给出解释,希望能启发您了解尝试此类任务所涉及的一般原则,同时提供特定于您正在尝试的任务的示例。

如果您刚刚开始编写浏览器扩展,请学习熟悉浏览器的开发工具,尤其是检查器。例如,在您当前的挑战中,如果您打开成绩单面板,使用您的检查器,您可以将鼠标悬停在面板上,并在那里找到带有时间戳的条目。如果单击其中一个条目,它将在检查器窗口中突出显示相应的 html 容器。现在您已进入感兴趣的代码范围。从那里您可以开始检查周围的代码,并获取感兴趣的代码的标签名称、类名和 id(如果存在)。

例如,当我检查脚本窗口时,我可以看到带时间戳的条目都是 div 容器,类名:

<div class="cue-group style-scope ytd-transcript-body-renderer">

进一步探查树,我看到包含所有这些条目的父级是专有标签:

<ytd-transcript-body-renderer class="style-scope ytd-transcript-renderer" standardized-themed-scrollbar="">

这个想法是找到包含您要搜索的所有项目的最深层容器。最深层次是因为您希望它包含所有感兴趣的内容,但仅此而已。然后使用 JavaScript 访问那些 DOM 项。关键是在您进行例如getElementsByClassName()getElementsByTagName() 查询时尽可能多地隔离感兴趣的代码,这样您就不会冒无意中拾取可能共享相同类或标签的不相关节点的风险名字。

话虽如此,我不知道getElementsByTagName() 是否会找到非标准标签,但如果你再上一层,就会有一个只包含<ytd-transcript-body-renderer> 容器的div,它有一个id:

<div id="body" class="style-scope ytd-transcript-renderer">

我发现非常有用的是右键单击感兴趣的最外层容器,然后选择“复制 outerHTML”。然后我通过online beautifier 运行它,然后将其粘贴到我的 HTML 编辑器中进行检查。例如,这样做会给我:

<div id="body" class="style-scope ytd-transcript-renderer">
  <ytd-transcript-body-renderer class="style-scope ytd-transcript-renderer" standardized-themed-scrollbar="">
    <!--css-build:shady-->
    <div class="cue-group style-scope ytd-transcript-body-renderer">
      <div class="cue-group-start-offset style-scope ytd-transcript-body-renderer">
        00:01
      </div>
      <div class="cues style-scope ytd-transcript-body-renderer">

        <div class="cue style-scope ytd-transcript-body-renderer" role="button" tabindex="0" start-offset="1020">
          You may have noticed recently that YouTube
          has changed their layout a bit.
        </div>
        <dom-repeat class="style-scope ytd-transcript-body-renderer"><template is="dom-repeat"></template></dom-repeat>
      </div>
    </div>

    <div class="cue-group style-scope ytd-transcript-body-renderer">
      <div class="cue-group-start-offset style-scope ytd-transcript-body-renderer">
        00:06
      </div>
      <div class="cues style-scope ytd-transcript-body-renderer">

        <div class="cue style-scope ytd-transcript-body-renderer" role="button" tabindex="0" start-offset="6830">
          This is as of September 2017.
        </div>
        <dom-repeat class="style-scope ytd-transcript-body-renderer"><template is="dom-repeat"></template></dom-repeat>
      </div>
    </div>

    ...
  
  </ytd-transcript-body-renderer>
</div>

您可以看到每个&lt;div class="cue-group style-scope ytd-transcript-body-renderer"&gt; 容器都包含后代,这些后代分别包含文本和时间戳。因此,一旦您访问入口容器,您现在就可以使用诸如getElementsByClassName() 之类的 DOM 方法定位并访问包含您想要的信息的独特容器。

因此,在您的内容脚本中,获取所有带时间戳的条目容器的列表:

// (It seems odd they would use an id of "body" for this, but AFAICT,
// this is unique to the document.)
let transcriptContainer = document.getElementById("body");
let transcriptEntries = tsContainer.getElementsByClassName("cue-group");

请注意,我使用.cue_group 进行类查询。我通过检查我们隔离块中所有类的使用来确定它对于这些容器来说是唯一的。

需要注意的一点是,检查并确保您找到的代码块不是加载到 iframe 中的文档的一部分(您可以为此使用检查器),在这种情况下,您必须使用方法来访问iframe 文档。然而,在这种情况下,脚本代码是根文档的一部分。

对于基本搜索:

// There are many ways you could optimize this, this is just a basic example.
function searchTranscript(query) 
  // Normalize the query string.
  query = query.trim().toLowerCase().replace(/ +/g, " ");

  let transcriptContainer = document.getElementById("body");
  let transcriptEntries = transcriptContainer.getElementsByClassName("cue-group");
  let tsMatches = [];

  for (let i = 0; i < transcriptEntries.length; i++) 
    let entry = transcriptEntries[i];
    let textContainer = entry.getElementsByClassName("cue")[0];
    let tsContainer = entry.getElementsByClassName("cue-group-start-offset")[0];

    // Normalize the text entry string.
    let text = textContainer.textContent.trim().toLowerCase().replace(/ +/g, " ");

    // Simple string search, you could get fancier here if you want.
    if (text.indexOf(query) > -1) 
      // If found, record the timestamp.
      tsMatches.push(tsContainer.textContent);
    
  

  return tsMatches;

当然,这只是我的示例,您可以通过多种方式访问​​所需的节点。

请注意,这可能会也可能不会取决于是否打开脚本窗口。它可能在关闭时存在于 DOM 中,只是它被隐藏了。你只需要四处逛逛。从本质上讲,这就是编写内容脚本所涉及的内容,需要进行大量的探索。也有可能,当页面加载时内容可能不在 DOM 中,一旦打开脚本面板,它会在再次关闭后保留在那里。在这种情况下,您总是可以以编程方式打开面板,然后关闭它(通常不会被用户检测到),就像我过去对此类事情所做的那样。但这是一个完整的教训。

警告:以这种方式依赖方法可能很脆弱,因为您依赖于网站而不更改您正在访问的代码。但是,我写了很多扩展,在这方面我很少遇到问题。有时它只是完成您想做的事情的唯一方法。

编辑:js 搜索代码现在已经过测试并且可以工作了。

【讨论】:

我在最后两段中做了一些重要的修改,所以如果您还没有,您可能需要重新阅读这些内容。

以上是关于我可以使用 Youtube API 在字幕轨道中搜索关键字/时间戳吗?的主要内容,如果未能解决你的问题,请参考以下文章

PHP:使用同步标志上传 YouTube v3 API 字幕

从 YouTube 视频中提取自动字幕

YouTube 字幕 API 返回不准确的数据

获取只有字幕 Youtube API 的视频列表

无法在 Youtube API v3 中下载隐藏式字幕

如何在 YouTube api v3 中获取 YouTube 视频的 cc 字幕