如何在不使用搜索的情况下查找 youtube 频道当前是不是正在直播?

Posted

技术标签:

【中文标题】如何在不使用搜索的情况下查找 youtube 频道当前是不是正在直播?【英文标题】:How to find if a youtube channel is currently live streaming without using search?如何在不使用搜索的情况下查找 youtube 频道当前是否正在直播? 【发布时间】:2019-10-16 12:57:01 【问题描述】:

我正在开发一个网站来加载多个 youtube 频道直播。起初我试图想办法在不使用 youtube 的 api 的情况下做到这一点,但我决定放弃。

要查找频道是否正在直播并获取我一直在使用的直播链接:

https://www.googleapis.com/youtube/v3/search?part=snippet&channelId=CHANNEL_ID&eventType=live&maxResults=10&type=video&key=API_KEY

但是,最小配额为 10000,每次搜索价值 100,在超出配额限制之前,我只能进行大约 100 次搜索,这根本没有帮助。我最终在大约 10 分钟内超过了配额限制。 :(

有没有人知道使用尽可能少的配额点来确定频道当前是否正在直播以及直播链接是什么的更好方法?

我想每 3 分钟为每个用户重新加载 youtube 数据,将其保存到数据库中,并使用我自己的 api 显示信息以节省服务器资源以及配额点。

希望有人能很好地解决这个问题!

如果无法对链接做任何事情,而只是确定用户是否在线而不每次使用 100 个配额点,那将是一个很大的帮助。

【问题讨论】:

"我想每 3 分钟为每个用户重新加载 youtube 数据,将其保存到数据库中,并使用我自己的 api 显示信息以节省服务器资源和配额点。"是的,这只是基本的缓存,正是我建议你做的。您甚至可以让您的脚本每次都进行查找,并在前面放置一个代理(例如 nginx)并让它自己缓存......将此问题转移到另一层。 您使用什么语言每 3 分钟抓取一次此数据并对其进行数据库化? 大家都知道我在不久前完成了这个项目的工作,并且能够在不需要 YouTube API 的情况下收集信息。我基本上使用 php html DOM 解析器设置了一个 cron 作业。 cron 作业运行所需的唯一细节是频道的 ID。获得信息后,我只需使用一个简单的查找功能来搜索 HTML 并收集所需的信息。一旦数据库试图收集更多的 1000 个频道信息,该方法确实开始使用大量带宽。我不必这样做,但如果 youtube 注意到您最终可能需要代理。 【参考方案1】:

由于该问题仅指定不应使用 Search API 配额来确定频道是否为流式传输,因此我想我会分享一种解决方法。与简单的 API 调用相比,它可能需要更多的工作,但它将 API 配额的使用减少到几乎没有:

我使用了一个简单的 Perl GET 请求来检索 Youtube 频道的主页。在直播的频道页面的 HTML 中可以找到几个独特的元素:

实时观看者标签的数量,例如<li>753 watching</li>LIVE NOW 徽章标签:<span class="yt-badge yt-badge-live" >Live now</span>.

要确定频道当前是否正在直播,需要进行简单匹配以查看唯一的 HTML 标记是否包含在 GET 请求结果中。类似:if ($get_results =~ /$unique_html/)(Perl)。然后,可以只对实际正在流式传输的频道 ID 进行 API 调用,以获取流的视频 ID。

这样做的好处是您已经知道频道正在流式传输,而不是使用数千个配额点来查找。我的测试脚本通过查看<span class="yt-badge yt-badge-live" > 的 HTML 代码成功识别频道是否正在流式传输(请注意来自 Youtube 的代码中奇怪的额外空格)。

我不知道 OP 使用的是什么语言,或者我会帮助处理该语言的基本 GET 请求。我使用 Perl,并包含浏览器标头、用户代理和 cookie,看起来就像普通的计算机访问。

Youtube的robots.txt似乎并没有禁止爬取频道的主页,只禁止爬取频道的社区页面。

让我知道您对这种方法的优缺点的看法,如果发现缺陷,请评论可能改进的地方,而不是不喜欢。谢谢,编码愉快!

2020 年更新 yt-badge-live 似乎已被弃用,它不再可靠地显示频道是否正在流式传输。相反,我现在检查这个字符串的 HTML:

"text":" watching"

如果我得到匹配,则表示该页面正在流式传输。 (非流媒体频道不包含此字符串。)再次注意奇怪的额外空白。由于我使用的是 Perl,所以我还转义了所有引号。

【讨论】:

我在 php 中使用 DOMDocument 编写了一个类似的脚本,但使用大量资源并需要很长时间才能完成时遇到了问题,file_get_contents 函数似乎也加载了相关的 css使用完全不需要的页面。如果我能阻止它这样做,那将是一个潜在的解决方案。你在 PHP 上使用 PERL 来发出这个请求的原因是什么? 占用了太多带宽?嗯,我想我没有大量的请求要找出答案。我正在为我的使用一个 DigitalOcean 5 美元的液滴,它有大约 1TB 的出站和免费的入站。我已经有一堆 Perl 脚本,所以我只是为此修改了一个。下面是一个带有浏览器标头的完整 PHP 请求的示例:beamtic.com/setting-request-headers-curl @Bman70 如果频道正在流式传输多个视频怎么办?如何选择我想要的?【参考方案2】:

以下是我的两个建议:

Check my answer 我解释了如何检查如何从正在直播的频道中检索视频。 另一种选择是使用以下 URL 并以某种方式每次都发出请求以检查是否有直播。

https://www.youtube.com/channel/<CHANNEL_ID>/live

CHANNEL_ID 是您要检查该频道是否正在直播的频道 ID1


1 请注意,该 URL 可能不适用于所有频道(这取决于频道本身)

例如,如果您检查 channel_id UC7_YxT-KID8kRbqZo7MyscQ - link to this channel livestreaming - https://www.youtube.com/channel/UC4nprx9Vd84-ly7N-1Ce6Og/live,则此频道将显示他是否正在直播,但是,他的频道 ID 为 UC4nprx9Vd84-ly7N-1Ce6Og - @ 987654324@ - 会显示他的主页。

【讨论】:

使用频道名称也可以像 https://www.youtube.com/c/<CHANNEL_NAME>/livehttps://www.youtube.com/user/<CHANNEL_NAME>/live 一样工作,如果频道名称中有空格,则删除任何空格 @Amineze 谢谢。我用这样的“Microsoft”进行了测试:https://www.youtube.com/c/Microsoft/livehttps://www.youtube.com/user/Microsoft/live,但这可能不适用于所有 YouTube 频道,如“NASAtelevision”:适用于 https://www.youtube.com/user/NASAtelevision/live,但不适用于 https://www.youtube.com/c/NASAtelevision/live 'user' 和 'c' 可以与其他频道不同,而 NASAtelevision 是用户名,NASA 是频道名称(并非所有频道都使用相同的名称)因此https://www.youtube.com/c/NASA/live , 适用于大写和小写。尽管使用带有频道名称的“c”确实不适用于某些频道,但 id 和用户名有效。这些方式的问题是,您无法为运行多个直播的频道选择重定向到哪一个,但最好验证频道是否正在直播并使用 API 检索直播的视频 ID . 在大多数情况下优于接受答案【参考方案3】:

除了Bman70 的答案之外,我在知道该频道正在直播后尝试消除发出昂贵搜索请求的需要。我使用来自正在直播的频道页面的 HTML 响应中的两个指标来做到这一点。

function findLiveStreamVideoId(channelId, cb)
  $.ajax(
    url: 'https://www.youtube.com/channel/'+channelId,
    type: "GET",
    headers: 
      'Access-Control-Allow-Origin': '*',
      'Accept-Language': 'en-US, en;q=0.5'
  ).done(function(resp) 
      
      //one method to find live video
      let n = resp.search(/\"videoId[\sA-Za-z0-9:"\\\]\[,\-_]+BADGE_STYLE_TYPE_LIVE_NOW/i);

      //If found
      if(n>=0)
        let videoId = resp.slice(n+1, resp.indexOf("",n)-1).split("\":\"")[1]
        return cb(videoId);
      

      //If not found, then try another method to find live video
      n = resp.search(/https:\/\/i.ytimg.com\/vi\/[A-Za-z0-9\-_]+\/hqdefault_live.jpg/i);
      if (n >= 0)
        let videoId = resp.slice(n,resp.indexOf(".jpg",n)-1).split("/")[4]
        return cb(videoId);
      

      //No streams found
      return cb(null, "No live streams found");
  ).fail(function() 
    return cb(null, "CORS Request blocked");
  );

但是,有一个权衡。此方法将最近结束的流与当前直播的流混淆。此问题的解决方法是获取从 Youtube API 返回的videoId 的状态(从您的配额中花费一个单位)。

【讨论】:

一个完整的解决方案在这里发布在一个要点中。 gist.github.com/MMujtabaRoohani/…【参考方案4】:

考虑到搜索操作的成本,我发现 youtube API 非常严格。显然,接受的答案对我不起作用,因为我也在非直播流中找到了字符串。使用 aiohttp 和 beautifulsoup 进行 Web 抓取不是一种选择,因为更好的指标需要 javascript 支持。因此我转向。我找了css选择器

#info-text 然后搜索字符串Started streaming 或其中包含watching now

为了减少原本需要更多资源的小型服务器上的负载,我将此功能测试移至带有小型烧瓶应用程序的 heroku dyno。

# import flask dependencies
import os
from flask import Flask, request, make_response, jsonify
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

base = "https://www.youtube.com/watch?v=0"
delay = 3
# initialize the flask app
app = Flask(__name__)

# default route
@app.route("/")
def index():
    return "Hello World!"

# create a route for webhook
@app.route("/islive", methods=["GET", "POST"])
def is_live():
    chrome_options = Options()
    chrome_options.binary_location = os.environ.get('GOOGLE_CHROME_BIN')
    chrome_options.add_argument('--disable-gpu')
    chrome_options.add_argument('--no-sandbox')
    chrome_options.add_argument('--disable-dev-shm-usage')
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--remote-debugging-port=9222')
    driver = webdriver.Chrome(executable_path=os.environ.get('CHROMEDRIVER_PATH'), chrome_options=chrome_options)
    url = request.args.get("url")
    if "youtube.com" in url:
        video_id = url.split("?v=")[-1]
    else:
        video_id = url
        url = base.format(url)
    print(url)
    response =  "url": url, "is_live": False, "ok": False, "video_id": video_id 
    driver.get(url)
    try:
        element = WebDriverWait(driver, delay).until(EC.presence_of_element_located((By.CSS_SELECTOR, "#info-text")))
        result = element.text.lower().find("Started streaming".lower())
        if result != -1:
            response["is_live"] = True
        else:
            result = element.text.lower().find("watching now".lower())
            if result != -1:
                response["is_live"] = True
        response["ok"] = True
        return jsonify(response)
    except Exception as e:
        print(e)
        return jsonify(response)
    finally:
        driver.close()

# run the app
if __name__ == "__main__":
   app.run()

但是,您需要在设置中添加以下构建包

https://github.com/heroku/heroku-buildpack-google-chromehttps://github.com/heroku/heroku-buildpack-chromedriverhttps://github.com/heroku/heroku-buildpack-python

在设置中设置以下 Config Vars

CHROMEDRIVER_PATH=/app/.chromedriver/bin/chromedriverGOOGLE_CHROME_BIN=/app/.apt/usr/bin/google-chrome

您可以找到受支持的 python 运行时 here,但任何低于 python 3.9 的版本都应该是好的,因为 selenium 存在不正确使用 is 运算符的问题

我希望 youtube 能提供比解决方法更好的替代方案。

【讨论】:

以上是关于如何在不使用搜索的情况下查找 youtube 频道当前是不是正在直播?的主要内容,如果未能解决你的问题,请参考以下文章

Youtube API V3 - 如何使用频道图标字段搜索视频?

如何让机器人在不使用命令的情况下向特定频道中的特定公会发送消息

如何在不发送任何内容的情况下检查频道是不是已满[重复]

如何在 youtube api 中跨多个频道搜索内容?

如何使用来自 YouTube Data API V3 的 enpoint 搜索通过 channelId 获取频道图标

Flutter - 如何在不每次下载flutter和dart sdk的情况下切换flutter频道