EveryInfra

EveryInfra Blog · BL-04

YouTube 评论抓取为什么漏回复:两层分页与完整性核对

YouTube commentThreads 不一定包含全部回复。结合官方文档,解释 comments.list、parentId、两层分页、去重与评论数量差异,给出可回放的采集验收方法。

YouTube 评论抓取漏回复,首先应检查是否只读取了 commentThreads。线程列表里附带的回复不一定完整;视频下的线程要分页,每条顶层评论下面的回复也可能要单独分页。把第一层翻完,不等于全部回复已经拿到。

这不是一个新问题。Baskaya 在 Stack Overflow 的历史讨论里就遇到了回复数与返回内容不一致。旧回答包含当年 Google+ 集成的猜测,不能拿来解释今天的故障。真正仍有参考价值的是它提出的问题:你在数线程、数评论,还是数接口附带的一部分回复?

本文按截至 2026 年 9 月 5 日的官方文档说明这一差别,并给出采集结果的核对方法。讨论的是 YouTube 官方接口契约,不是 EveryInfra 的实测报告,也不承诺恢复已不可访问的内容。

commentThreads 里的 replies 为什么不全

YouTube 的资源说明里,一个 commentThread 包含顶层评论及可能附带的回复。顶层评论位于 snippet.topLevelComment,回复列表位于 replies.comments;后者可能只是子集。snippet.totalReplyCount 是该顶层评论的回复计数,不能直接拿整个线程列表的长度与它比较。

假设一个合成线程声明有 12 条回复,但当前对象只附带 5 条。这里的 5 不是采集程序可以宣布完成的数量,也不是把返回数组补成 12 个元素的理由。正确动作是继续检查该父评论的回复入口,而不是重复请求同一个线程对象,希望它下一次自动多给一些。

另一个容易忽略的区别是线程 ID 与顶层评论 ID。应从对应资源中分别读取并记录,不能因为某个样本里两串值相同,就在程序里认定它们永远可以互换。数据模型里的“看起来一样”,不等于接口契约里的“用途相同”。

顶层评论与回复,分别维护分页进度

官方 commentThreads.list 支持按 videoId 读取线程,用响应的 nextPageToken 继续取下一页。maxResults 的上限是单页最多 100 项,不是请求一次就获得视频的全部评论,也不是一条线程最多只有 100 条回复。

针对某条顶层评论,再按 comments.list 的规则,把该评论的 ID 传入 parentId,并处理这次回复查询自己的 nextPageToken。这里的分页令牌属于回复查询,不能塞回视频线程查询,也不能拿另一条父评论的令牌继续使用。

实现时可以分成两个任务层次:视频任务发现顶层评论,父评论任务负责补取回复。每个父评论维护自己的继续位置与结束原因。这样,一条回复特别多的讨论不会把其他已完成的线程变成“全部重来”。

请求条件也应和进度一起保存。例如是否使用了 searchTerms、选择何种排序、目标视频是什么。若一开始限定了关键词,后面的完整性声明就只能覆盖这个查询范围;不能在导出文件里省掉筛选条件,变成“该视频全部评论”。

把完成条件写进结果,不只写在日志里

建议将评论内容与采集进度分开。内容按平台和评论 ID 去重,保留视频、父评论归属及观察时间;进度则描述本次任务走到了哪里。这是应用侧设计建议,以下字段不是 YouTube 或 EveryInfra 的原始返回字段。

一个最小进度记录可以回答:线程列表是否已到末页、哪些父评论的回复已到末页、哪些因为请求失败或任务预算而停止、最后一次确认时间是什么。只写 success: true,无法表达“顶层已完成,但一条父评论还有两页未取”。

去重应发生在保存内容时。某一页写入后进程退出,恢复时可能再次拿到同一页;按 ID 更新已见记录,比按评论文本判重更可靠。两个人写出相同短句仍是两条评论,同一条评论编辑文字也不应立刻变成两个独立用户意见。

进度推进要落在内容保存成功之后。若先记下下一页、再保存当前页,中途失败就可能跳过数据。允许重读并去重,通常比允许静默跳页更容易核对。游标重复或长时间没有新 ID 时,应停止并标记异常;不要把不断重试同页当作有效进展。

评论数对不上,哪些差额不能“修复”

先检查对象层级、筛选条件和两层分页,再看错误类型。线程列表官方文档区分 commentsDisabled、权限不足和找不到视频等情况。这些都不应被包装成“视频当前没有评论”。一个空数组若来自成功的查询,与一个被客户端吞掉异常后返回的空数组,不是同一份证据。

对于数量差异,还应记录观察窗口:页面上看见的计数、线程元数据和分页结果,未必取自同一时刻。仅凭一个计数差额,不能确认是哪条内容缺失,更不能替它编写正文。需要分析可读文本时,就以实际获得的、可归属的评论为样本,同时公开未完成范围。

我们建议将状态写成可核对的话,例如“本次查询的线程页已结束,已发现父评论中有两项回复任务未完成”。相比“完整率 98%”,这更诚实,因为后者需要一个可靠且同口径的总量分母。没有分母,就不要制造一个看似精确的百分比。

如果是通用 HTTP 或参数问题,可接着看站内的 API 报错处理。重试能处理一部分临时失败,但不能扩大权限,也不能让一个部分回复对象变成完整数据集。

用四个小样本检查采集器

在有权访问的测试对象或合成夹具上,先检查四个场景,比一开始跑大量视频更容易定位错误。

  1. 一条线程附带的回复少于声明数量:程序应建立回复任务,而不是直接宣告完成。
  2. 某条回复列表有下一页:程序应继续该父评论,不能只翻视频线程列表。
  3. 相同页面被读取两次:最终唯一评论数不应翻倍,父子关系应保持一致。
  4. 某个父评论请求失败:其他结果可以保留,但导出必须说明局部未完成,不能用空回复覆盖旧的有效结果。

验收还要检查输出用途。做主题归类时,回复中的“这个不对”如果脱离父评论,几乎没有可解释性。把父评论关联保留下来,能帮助人或模型回到讨论上下文。站内抖音评论与回复关系也讨论了这个建模问题,但不同平台的请求参数不能互抄。

上述是建议测试,不是本文已运行的真实视频实验。若你已有接口导出的 JSON,可以先离线核对这些结构与状态,再决定是否需要获准补取数据。

真正要交付的是一个有边界的评论样本

“抓到了多少条”只是结果的一部分。一个可用于分析的导出,还应说明目标、筛选、采集时间、父子关系、去重方式和未完成项。分析报告也应继承这些边界,不能因为进入了总结环节,就丢掉上游已经知道的缺口。

选择封装后的数据接口时,可以借助统一数据 API 入门里的目录核对方法,逐项确认它实际提供什么字段和分页能力。不要把本文的官方参数直接套用到另一套 API,也不要把“支持评论”理解为所有层级、所有历史内容都已覆盖。

YouTube 评论采集的关键,不是不断调大一页的数量,而是让每条回复的归属、每个分页任务的进度、每次停止的原因都能被解释。只有这样,抓取结果才能成为分析的依据,而不是一份无法说明自己漏了什么的文件。