YouTube 视频 Onebox 嵌入功能停止工作

几天前(我说是刚更新到 Discourse 2.5 beta 5 之后,并在 beta 6 中持续出现,但这可能只是巧合),YouTube 视频的嵌入功能间歇性失效。
在几小时无法工作后,它又自动恢复正常嵌入。
但从昨天起,该功能完全停止工作。

我在论坛日志中没有发现任何可疑的具体错误。

这是否可能是超时问题?有没有办法专门调查这个问题?

提前感谢!

我在检查请求包含 YouTube 链接的帖子重新渲染时产生的流量时发现了一些异常情况。
该请求以 404 错误失败。:thinking:

再补充一点细节。
我运行的是 Discourse 2.5.0.beta6,已更新至提交版本 2d880b42a3(在发送此帖前刚刚重新构建)。

在使用 Google Chrome 控制台创建包含 YouTube 链接的新帖子时,在发起对 onebox 的 GET 请求并返回 404 错误之前,会多出一条与服务 Worker 注册相关的错误。

我想指出的是,除了 YouTube 之外,所有一其他 onebox 都能正常工作。:confused:

浏览器无法告知单框失败的真正原因。您需要尝试从服务器发起类似的请求。

我理解你的建议,但是:

  • 如果我简单地重复 GET 请求(按预期传递 headers 等信息)到 /onebox?url=…,我只会得到 404 错误 HTML。

  • 如果我在服务器上重复 onebox 发出的请求,从 youtube_onebox.rb 的 Ruby 代码中复制,即 curl 'https://www.youtube.com/oembed?format=json&url=https://www.youtube.com/watch?v=Xl-PTTeRsik',针对某个未嵌入的视频,它似乎可以完美运行。
    该调用实际返回:
    {"author_name":"AstronautiCAST","version":"1.0","height":270,"author_url":"https:\/\/www.youtube.com\/user\/AstronautiCAST","provider_name":"YouTube","provider_url":"https:\/\/www.youtube.com\/","thumbnail_height":360,"width":480,"html":"\u003ciframe width=\"480\" height=\"270\" src=\"https:\/\/www.youtube.com\/embed\/Xl-PTTeRsik?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen\u003e\u003c\/iframe\u003e","type":"video","thumbnail_width":480,"title":"Loading cargo into HTV-9 Konutori","thumbnail_url":"https:\/\/i.ytimg.com\/vi\/Xl-PTTeRsik\/hqdefault.jpg"}root@fait-2020:/var/discourse#
    其中包含了 onebox 嵌入所需的所有内容。

我不明白这是怎么回事 :confused:

@Falco 非常抱歉打扰您,但 YouTube 视频嵌入功能的缺失严重影响了我们论坛的用户体验。

昨天我尝试了多种方法,例如阅读源代码(以了解在什么阶段、何种条件下会触发 onebox 返回 404 错误),甚至将当前生产环境的论坛克隆到一个全新的 Digital Ocean 服务器上。

阅读代码并没有太大帮助,主要是因为我对该平台的了解有限。
这里还有一个问题悬而未决:onebox 的处理过程是否有日志记录?如果能了解导致 onebox 放弃并返回 404 的具体错误,将会非常有帮助。

克隆 Droplet 的初衷是检查生产服务器的 IP 是否被 YouTube 屏蔽,同时排查是否与我们使用的域名有关。
然而,即使 IP 地址和域名都已更改,YouTube 视频嵌入功能仍然无法正常工作。

我真心希望能有人建议至少一种方法来追踪 onebox 的具体行为。

我已经找到了根本原因:YouTube 封禁了我们的 IP,但具体原因尚不清楚,因为事发时我们并未进行大规模重新生成(rebake)或类似操作。

请问 @Falco@codinghorror:安装 2.5.0beta 5 或 6 是否触发了任何重新生成操作,从而超出了请求限制?

没有,我们最近没有添加任何重新渲染的内容。这听起来像是 YouTube 的变动,因为其他用户也抱怨了同样的问题。

尝试使用 "Onebox Assistant", crawl for those previews reliably! 进行代理抓取。

感谢大家的回复。
不过我仍然不太明白,为什么当我模仿 Onebox 引擎发出的请求时(至少我预期会是这样,是 @Falco 吗?),我得到的却是包含正确响应的 JSON 数据,而不是 429 错误。

在按照我的截图执行请求之前,Onebox 是否还发出了另一个请求并收到了 429 错误?该请求如下:
curl 'https://www.youtube.com/oembed?format=json&url=https://www.youtube.com/watch?v=Xl-PTTeRsik'

不用说,这些请求都是从运行 Discourse 的同一台服务器发出的(因此具有相同的外出 IP 地址)。

单个 curl 命令不太可能触发速率限制?

试试连续快速执行多个?或者在重复的 Bash 脚本中运行?(但不要太多,否则可能会被封禁)

好吧,仅仅尝试用一条包含 YouTube 链接的帖子重新生成,就会让 onebox 返回 404 错误……

经过一个周末的故障排查,发现了一个有趣的现象。
@Falco@codinghorror,或许你们可以关注一下这个问题?

目前,在阅读 youtube_onebox.rb 的源代码时,我发现它支持从以下三种 URL 中提取视频 ID:

  1. http://youtu.be/<videoid>
  2. https://www.youtube.com/embed/<videoid>
  3. https://www.youtube.com/watch?v=<videoid>

尝试对格式为 1 和 3 的链接进行 onebox 处理时均失败,onebox 返回 404 错误(我认为这可能与我们的 IP 被屏蔽有关)。

但当我尝试嵌入格式为 2 的链接时,却能正常工作!

我在想,这是否与 这篇帖子 中解释的情况有关。

如果能了解 onebox 的内部工作机制(例如它向 YouTube 发起了哪些具体调用),并配合日志记录,将会非常有帮助……

回来给这个帖子做个收尾。
昨天 YouTube 解除了我们的封禁,我们已恢复正常运营。

不过,有几点我认为还是值得探讨一下:

  • 能否让 onebox 在 oneboxing 失败时记录详细日志?这将非常有助于我们准确了解失败原因。
  • 既然对 YouTube 视频进行 oneboxing 唯一必需的元素是视频 ID,那么在返回 404 错误之前,尝试从三种 URL 类型中提取数据是否是个好主意?(如上所述,/embed/ 类型的 URL 不知何故从未停止工作,即使在被封禁期间也是如此。)

感谢所有回答我那些惊慌失措的问题的朋友们!:clap:

完全赞同!!!(这是一个非常好的建议,我之前也提到过)

我想这里的部分挑战在于,如果目标页面返回的网页中没有 og 标签,Onebox 将无法判断这是否是由于某种重定向导致的。不过,这种情况可以被记录(例如:“ONEBOX:未找到 og 标签”),所有导致 Onebox 显示为空白的所有错误都应该被记录。

tl;dr 我想补充一点,我们这里似乎遇到了相同的问题。如果由于最近的某些变更导致存在速率限制问题,那么我认为其他用户在迁移、重新烘焙帖子时,或者仅仅因为论坛非常繁忙时,也会开始遇到这个问题。事实是,onebox 看似静默失败,这意味着这些问题直到用户开始抱怨缺少 YouTube onebox 时才会显现出来。

背景

我们当前使用的是 2.6.0.beta 1 版本。

用户收到了关于非安全内容的警告。经过调查,Chrome 似乎在抱怨来自 HTTP 站点链接的图片。因此,我将 Discourse 配置为下载所有图片/媒体并通过 HTTPS 提供服务。

更改该设置后,这意味着需要对历史帖子进行重新烘焙。自那次重新烘焙以来,之前被 onebox 化的大量 YouTube 视频现在又变回了链接 URL。

我们有一个包含 10,000 条回复的线程,其中全是 YouTube 视频回复,且所有帖子都显示为 URL 而非 onebox。

在重新烘焙过程中,所有排队作业都正常处理,因此并非有作业卡在已删除作业的队列中。

我没有看到 @marcozambi 描述的那些错误消息,但我相信我们也触发了速率限制。

我已尝试过的方法

为了支持这一速率限制理论,我编写了一段小程序来重新烘焙帖子,该程序在某个线程的前 80 多个 YouTube 视频中成功实现了 onebox 化,但随后无法转换剩余的視頻。

此时,即使编辑帖子、进行小幅修改并重新保存,也无法强制 URL 被 onebox“展开”。与此同时,所有队列均为空,或者正如我所预期的那样,只有少量作业被即时处理。

在 30 分钟内多次尝试重新运行该代码,均无法强制对这些链接进行 onebox 化。我认为 80 并非一个神奇的数字,这只是我们可用配额所能支持的数量。

@marcozambi 提到,当其他链接失败时,使用 /embed/ 格式的 YouTube 链接可以正常工作,因此我修改了代码,使用正则表达式搜索并替换 YouTube 链接,将其转换为 /embed/ 格式。

代码生效了。

但再次运行代码仅重新烘焙帖子时,却无法将其转换为 onebox 表示形式。

我的计划是尝试创建一个任务,将大型线程中的所有 YouTube 链接转换为 /embed/ YouTube 格式。如果该方案失败或我们触发了更高的速率限制,我将查看 @merefield 的 Onebox Assistant。

我稍后会发布更新。

好的,确实有些奇怪的情况正在发生,看起来与速率限制有关。

我不确定我们是因为进行了大规模的重建(rebake)而被列入“惩罚名单”导致被速率限制,还是我们触发了其他人也会遇到的限制。

YouTube 视频的 Oneboxing(一键嵌入)似乎存在限制,一旦达到该限制,Oneboxing 就会静默失败。

出于显而易见的原因,我认为必须对此进行修改,特别是对于那些进行迁移或重建的人来说,他们可能完全不知道大量未展开或曾经展开的 Onebox 现在已变成了普通 URL。

@marcozambi 在上面提到,当其他格式因(推测是)速率限制问题而失败时,包含 /embed/ 的视频 ID 格式的 YouTube URL 仍然有效。

以下视频很好地说明了这一现象。

在录制此屏幕录像时,队列中没有积压的任务,论坛整体运行良好。

在此视频之前,YouTube 链接已开始无法被 OneBox 展开。

您将看到的是一个撰写窗口,其中 Onebox 无法展开 https://youtu.be/<video-id> 格式的 YouTube 链接。

随后我将格式更改为 https://youtube.com/embed/<video-id>,Onebox 成功展开了它。

然后我再次尝试原始格式,结果失败了。

在录制此视频期间,我追踪了浏览器控制台和网络标签页。我意识到问题肯定出在我们的服务器与 YouTube 之间,而不是我的浏览器与我们的服务器之间,但我仍将其附在下面,以防它们有用。

(抱歉图片有些缩小——希望放大后能看清)

以下是 Onebox 成功时的网络追踪记录。

我并不能完全信服 /embed/ 格式的链接在此处是万能药。我认为这似乎是一条拥有独立速率限制的路线:当 https://youtu.be/<video-id> 路线触及限制时,https://youtube.com/embed/<video-id> 路线则拥有另一套独立的限制。两者均受限的证据来自我编写的一个实用工具,该工具用于更改一个包含 1 万条帖子、其中 99% 为 YouTube 视频回复的巨型帖子线程中的 YouTube 嵌入格式。此时,Onebox 已经无法展开 https://youtu.be/<video-id> 格式的链接。我的工具将 YouTube 视频 URL 更改为 https://youtube.com/embed/<video-id> 格式,并对该线程的前 3000 条帖子进行了处理。它在前 1108 条帖子中运行良好,随后虽然成功更改了接下来约 1900 条帖子的格式,但 Onebox 并未展开它们。在此期间,生成了大量任务(我的代码使用了 post.revise),所有任务均无错误或重试地处理完毕。据我观察,任务处理在某个阶段似乎急剧加速。我猜测这可能是因为 Onebox 代码迅速从 YouTube 收到了某种错误,但我并未计时,也可能是其他多种原因。我很乐意提供更详细的证据,但如果不对 Onebox gem 进行仪器化,我不确定自己能做些什么。我是一名黑客而非 Ruby 专家,但我很乐意遵循一些高层指导。

从服务器命令行使用相同的用户代理执行一些简短的重复 curl 脚本,可能有助于隔离速率限制问题。

同意该变通方法可能有效,仅仅是因为它触发了独立的计数。

这里有一些更多的结果。请注意,下面的帖子中包含许多假设——基于对实际情况缺乏了解。

我会在该帖子之后补充我对当前情况的看法以及应该采取的措施。

感谢你的回复,Robert。

需要注意的是,当我尝试时,使用 /watch 路由对视频进行 Oneboxing(即生成预览卡片)一直失败(至今仍然如此),因此我不需要循环来强制其失败。

因此,我做出的一个假设是:Onebox 使用的 user-agentDiscourse Forum Onebox v2.6.0.beta1,依据是以下代码:

我随机选择了一个视频,并尝试使用 curl 读取其响应头。

我是在线上站点的 Docker 容器内执行此操作的,得到了以下响应。

第一次使用 /watch? 路由的 curl 结果

命令

curl --user-agent "Discourse Forum Onebox v2.6.0.beta1" -sD - -o /dev/null "https://m.youtube.com/watch?v=s0ONj4TG0UA"

响应

curl --user-agent "Discourse Forum Onebox v2.6.0.beta1" -sD - -o /dev/null "https://m.youtube.com/watch?v=s0ONj4TG0UA"
HTTP/2 303 
content-length: 0
p3p: CP="This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl=en-GB for more info."
cache-control: no-cache
x-frame-options: SAMEORIGIN
content-type: text/html; charset=utf-8
location: https://www.youtube.com/watch?v=s0ONj4TG0UA&app=desktop
accept-ch-lifetime: 2592000
x-content-type-options: nosniff
accept-ch: DPR
expires: Tue, 27 Apr 1971 19:44:06 GMT
strict-transport-security: max-age=31536000
date: Fri, 07 Aug 2020 11:35:21 GMT
server: YouTube Frontend Proxy
x-xss-protection: 0
set-cookie: VISITOR_INFO1_LIVE=rcVTSJn81Ck; path=/; domain=.youtube.com; secure; expires=Wed, 03-Feb-2021 11:35:20 GMT; httponly; samesite=None
set-cookie: YSC=cFXIPerzT3Y; path=/; domain=.youtube.com; secure; httponly; samesite=None
set-cookie: GPS=1; path=/; domain=.youtube.com; expires=Fri, 07-Aug-2020 12:05:20 GMT
alt-svc: h3-29=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"

因此,我收到了一个 303 重定向响应,重定向到 location 头中指定的 URL:https://www.youtube.com/watch?v=s0ONj4TG0UA&app=desktop

这仅仅是在 URL 后附加了 &app=desktop

第二次 curl 请求重定向后的 URL —— 仍使用 /watch? 路由

命令
curl --user-agent "Discourse Forum Onebox v2.6.0.beta1" -sD - -o /dev/null "https://www.youtube.com/watch?v=s0ONj4TG0UA&app=desktop"

响应

HTTP/2 429 
x-content-type-options: nosniff
expires: Tue, 27 Apr 1971 19:44:06 GMT
x-frame-options: SAMEORIGIN
cache-control: no-cache
p3p: CP="This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl=en-GB for more info."
accept-ch-lifetime: 2592000
content-type: text/html; charset=utf-8
accept-ch: DPR
strict-transport-security: max-age=31536000
content-length: 48982
date: Fri, 07 Aug 2020 11:46:00 GMT
server: YouTube Frontend Proxy
x-xss-protection: 0
set-cookie: VISITOR_INFO1_LIVE=VQwNuouhq-s; path=/; domain=.youtube.com; secure; expires=Wed, 03-Feb-2021 11:46:00 GMT; httponly; samesite=None
set-cookie: YSC=8IRfPRFRY6c; path=/; domain=.youtube.com; secure; httponly; samesite=None
set-cookie: GPS=1; path=/; domain=.youtube.com; expires=Fri, 07-Aug-2020 12:16:00 GMT
set-cookie: VISITOR_INFO1_LIVE=VQwNuouhq-s; path=/; domain=.youtube.com; secure; expires=Wed, 03-Feb-2021 11:46:00 GMT; httponly; samesite=None
set-cookie: YSC=8IRfPRFRY6c; path=/; domain=.youtube.com; secure; httponly; samesite=None
set-cookie: GPS=1; path=/; domain=.youtube.com; expires=Fri, 07-Aug-2020 12:16:00 GMT
alt-svc: h3-29=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"

因此,我收到了一个 429“请求过多”的响应码,但没有收到 retry-after 头——直接停止,没有任何协商余地。

无论如何,如果 Onebox 看到的是这种情况,它要么忽略了该响应,要么至少我不知道如果它被记录了,应该去哪里查找。

虽然单个 429 响应可能是合理的行为,但在极短时间内收到大量 429 响应是绝不能被忽视的。

第三次 curl 结果 —— 这次使用 /embed/ 路由

为了完整性,我立即尝试获取同一个视频,但这次使用 /embed/ 路由。

命令

curl --user-agent "Discourse Forum Onebox v2.6.0.beta1" -sD - -o /dev/null "https://www.youtube.com/embed/s0ONj4TG0UA"

响应

HTTP/2 200 
accept-ch-lifetime: 2592000
content-type: text/html; charset=utf-8
expires: Tue, 27 Apr 1971 19:44:06 GMT
x-content-type-options: nosniff
cache-control: no-cache
p3p: CP="This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl=en-GB for more info."
strict-transport-security: max-age=31536000
accept-ch: DPR
date: Fri, 07 Aug 2020 11:55:29 GMT
server: YouTube Frontend Proxy
x-xss-protection: 0
set-cookie: VISITOR_INFO1_LIVE=PNE6x6djF00; path=/; domain=.youtube.com; secure; expires=Wed, 03-Feb-2021 11:55:29 GMT; httponly; samesite=None
set-cookie: VISITOR_INFO1_LIVE=PNE6x6djF00; path=/; domain=.youtube.com; secure; expires=Wed, 03-Feb-2021 11:55:29 GMT; httponly; samesite=None
set-cookie: GPS=1; path=/; domain=.youtube.com; expires=Fri, 07-Aug-2020 12:25:29 GMT
set-cookie: YSC=pDW-hdbauK8; path=/; domain=.youtube.com; secure; httponly; samesite=None
alt-svc: h3-29=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
accept-ranges: none
vary: Accept-Encoding

200 - 成功。

lazy-yt 插件似乎会重写为 /watch 格式的 URL

不确定这是否有意义,但……默认情况下是否启用了 lazy-yt 嵌入插件?我在开发环境中注意到了它。

它似乎对 YouTube Oneboxer 的 to_html 方法进行了猴子补丁(monkey patch)。

我不知道这是否重要,但原始 Onebox 的 to_html 方法返回的是 /embed/ URL 格式:

而 lazy-yt 插件使用的是 /watch?v= URL 格式。

我还能做些什么来证明存在一个需要关注的问题吗?下一篇文章将解释我认为的根本原因。