← 記事一覧へ

消えた DOM マーカーに依存しないコメント収穫
― 「番号」ではなく「身元+本文」で受理する構造ゲート

ライブ配信ページの DOM からコメントを収穫するとき、受理判定を「コメント番号セルの有無」に依存させると、サイト改修でそのセルが消えた瞬間に全コメントが取りこぼしになる。本記事は、番号という偶発的マーカーではなく身元+本文という本質で受理し、サイトが供給する構造ラベル(protobuf の oneof 相当)を誤検知ガードに使う再利用可能な設計を示す。あわせて「DOM に存在しない識別子はいくら DOM を緩めても取れない(匿名 ID はストリーム層にしか無い)」という、緩和の前に実機で確かめるべき観測を記録する。

公開日: 2026-06-18 著者: 君斗りんく / Kimito-Link Project ライセンス: MIT 実装: GitHub

1.解決したい課題

ライブ配信ページのコメント一覧を、ブラウザ拡張が独自に DOM から収穫して集計・可視化することがある。多くの実装は「1 行=コメント」を見分けるために、行内のコメント番号セル(連番が入る小さな要素)の存在を受理条件にしている。番号があれば本物のコメント、無ければスキップ、という素直な判定だ。

ところがこの設計には構造的な脆さがある。配信サイトは前触れなく DOM を改修する。ある日、コメント行から番号セルだけが取り除かれると、次のことが起きる:

  • 受理条件「番号セル+本文セルの両方必須」が満たされず、全行が取りこぼしになる。
  • 収穫経路の観測値が visible:0 に落ち、コメントは画面に出ているのに拡張側だけ「0 件」になる。
  • 番号セルに依存して付与していた付随情報(ユーザー識別子など)も同時に途切れる。

つまり、偶発的に存在していた DOM マーカーに受理判定を結びつけたことが原因で、サイト改修一発で機能が全停止する。本手法は、この依存を断ち切る。

2.手法の概要

鍵となる観点は 1 つ。「番号」はコメントである証ではない。コメントの本質は「身元(誰が)+本文(何を言ったか)」であって、連番は表示上の便宜にすぎない。だから受理ゲートも本質で組む。

収穫パイプラインの構造:

配信ページの DOM(コメント一覧・仮想スクロール)
   ↓
行候補を列挙(table-row 相当)
   ↓
★ 受理ゲート(本手法): 本文あり & 構造ラベルあり(番号は任意)
   ↓
身元・本文・付随情報を抽出
   ↓
重複排除して集計層へ

受理判定を 1 つの純粋関数に集約するのが要点。番号必須のロジックが収穫経路の複数箇所(行パース・祖先たどり・サブツリー収集など)に散らばっていると、片方だけ直して挙動がドリフトする。判定を 1 か所に正本化し、全経路がそこへ委譲する。

3.受理ゲートの設計

3-1. 受理条件(番号は任意・本文は必須)

  1. 本文セルがあること(必須)。本文の無い行はコメントではない。
  2. 次のいずれかを満たすこと:
    • 番号セルがある(従来どおりの本物のコメント行)、または
    • 番号セルが無くても、サイト供給の構造ラベル(例: data-comment-type 属性)を持つ。
  3. 推薦枠・広告カードなど別セクション内の DOM を除外する(後述の構造ガード)。

3-2. なぜ「構造ラベル」を誤検知ガードに使うのか

番号必須を単純に外すと、配信ページ内の「おすすめ番組カード」などコメントではない行を誤って拾うリスクが上がる。これを防ぐのが、サイト自身がコメント行に付ける構造ラベルだ。本物のコメント行には data-comment-type="normal" のような属性が付き、推薦カードには付かない。これは「文字列パターンで推測する」ヒューリスティックではなく、サイトが意味づけした構造情報なので堅い。

この発想は、ストリーム層(protocol buffers)で comment と gift と system message をoneof フィールド名で構造的に分岐する手法と同型である(後述の先行技術)。DOM 層における oneof 相当が、この構造ラベル属性だ。

3-3. 値検証は受理と分離する

「番号の桁が正しいか」「本文が空白だけでないか」といった値の検証は、受理ゲート(構造の有無を見る)とは別レイヤーに置く。受理ゲートは「この行は収穫対象か」だけを判定し、値の正規化は抽出側で行う。これにより、経路ごとに異なる値検証の有無を変えずに受理だけ正本化できる。

4.参考実装(JavaScript / 純粋関数)

受理判定を 1 つの純粋関数に集約する。requireNumber フラグで「従来どおり番号必須」と「番号任意(構造ラベルで受理)」を切り替えられるようにし、段階導入と後方互換を両立する。

/**
 * 1 行が「収穫対象の本物のコメント行か」を判定する純粋関数。
 * 受理ゲートを 1 か所に正本化し、収穫経路の各所がここへ委譲する。
 */
export function isHarvestableCommentRow(row, opts = {}) {
  if (!row || row.nodeType !== 1) return false;
  const requireNumber = opts.requireNumber !== false;  // 既定は従来挙動
  const guardSections = Boolean(opts.guardRecommendedSections);

  // 推薦枠・広告カードなど別セクション内は除外(構造ガード)
  if (guardSections && isInsideRecommendedSection(row)) return false;

  // 本文は必須
  if (!row.querySelector?.('.comment-text')) return false;

  const hasNumber = Boolean(row.querySelector?.('.comment-number'));
  if (requireNumber) return hasNumber;

  // requireNumber:false — 番号が無くても、サイト供給の構造ラベル
  // (oneof 相当)を持つ行は本物のコメントとして受理する。
  if (hasNumber) return true;
  return Boolean(row.getAttribute?.('data-comment-type'));
}

段階導入の勘所: 第 1 段階では requireNumber:true 既定のまま、散らばっていた番号必須ロジックをこの関数へ委譲だけする(挙動完全不変・既存テストが等価性を担保)。番号任意への緩和(requireNumber:false)は、後述の前提検証ゲートを通してから第 2 段階で開放する。

5.なぜこの設計なのか(原則)

  1. 受理は本質で組む(番号 ≠ コメントの証) ― コメントの本質は身元+本文。連番は表示の便宜にすぎず、サイト改修で消えうる。本質に依存すれば改修耐性が上がる。
  2. 受理ゲートは 1 か所に正本化する ― 同じ判定が複数経路に散ると、片方だけ直して挙動がドリフトする。1 つの純粋関数に集約し全経路が委譲する。
  3. 誤検知ガードは「推測」でなく「サイト供給の構造」で ― 文字列パターンで「コメントっぽい」を推測すると推薦カードを誤検知する。サイトが意味づけした構造ラベル(oneof 相当)を使う。
  4. 受理と値検証を分離する ― 「収穫対象か(構造)」と「値が妥当か(桁・空白)」は別レイヤー。受理だけを安全に正本化できる。
  5. 段階導入で退化ゼロ ― まず挙動不変の正本化(既存テストで等価性担保)、次に前提検証を経てから緩和。一気に変えない。
  6. 緩和の前に前提を実機で検証する ― 「マーカーが消えた」という前提自体が誤りのことがある(後述)。前提検証を依存ゲートとして手順に組み込む。

6.緩和の前に実機で確かめる(前提検証ゲート)

「番号セルが消えたから受理を緩める」は一見もっともだが、緩和を実装する前に実機 DOM で前提を検証すべきだ。本手法を実配信ページ(コメント大量・大半が匿名)で確認したところ、2 つの重要な事実が分かった。

6-1. 「マーカーが消えた」が常に真とは限らない

検証した配信では、観測したコメント行すべてに番号セルが存在していた(取りこぼし 0 件)。「現行はマーカーを外した」という当初の前提は、その配信では再現しなかった。マーカー消失はページ状態・配信種別・レイアウト等に依存する条件付きの現象でありうる。前提が真でない場所で受理を緩めれば、働いているガードを弱めるだけで利得はゼロになる。

6-2. DOM に無い識別子は、ゲートを緩めても取れない

さらに重要な観測として、匿名コメント行にはDOM/フレームワーク内部状態のどこにもユーザー識別子が存在しなかった(行サブツリーの内部状態を多段にたどっても識別子は皆無)。これは、受理ゲートをいくら緩めても匿名話者を「誰」として扱えないことを意味する。匿名識別子は配信プロトコル(protobuf ストリーム)にしか無いのであり、その経路で取得すべき問題だった。

教訓: 「DOM 受理ゲートを緩める」と「DOM に存在しない情報を取る」は別問題。前者をいじっても後者は解決しない。緩和の前に実機で前提(マーカー有無・必要情報の所在)を確かめることが、無駄な実装と機能退化を同時に防ぐ。

7.関連する既知技術(先行技術)

手法 / システム受理の基準曖昧さの解消本手法との関係
本手法(DOM 収穫)身元+本文サイト供給の構造ラベル
protobuf oneof 分岐フィールドの存在oneof フィールド名構造ラベルの源流(DOM 版が本手法)
セマンティック HTML / ARIArole 属性role="row" 等構造で意味を読む同系統
マイクロデータ / schema.orgitemtypeitempropサイト供給の構造を信頼する点が共通
HTML サニタイザのホワイトリスト許可タグ/属性許可リスト「推測でなく明示的許可」の思想が共通

7-1. protocol buffers の oneof 分岐

ストリーム配信のメッセージ解析では、1 つのメッセージが comment / gift / system のどれかを oneof フィールドで表す。受理側は "chat" in msg / "gift" in msg のようにフィールド名で構造的に分岐し、本文や種別を推測しない。本手法の構造ラベル(data-comment-type)は、この oneof 分岐を DOM 層に持ち込んだものに相当する。

7-2. セマンティック HTML / ARIA role

role="row"role="gridcell" など、DOM に付与された意味情報を読み取って構造を理解する手法。クラス名の文字列推測より安定する。本手法の「構造ラベルを信頼する」方針と同系統。

7-3. 「推測でなく明示的許可」のサニタイザ思想

HTML サニタイザがタグ・属性の許可リストで安全性を担保するのと同様、本手法も「コメントっぽい」を推測せず、サイトが明示した構造ラベルを受理根拠にする。誤検知を構造で排除する点で思想が一致する。

8.本記事の位置づけ・ライセンス

本記事は、Chrome 拡張機能『君斗りんくの追憶のきらめき』内のコメント収穫モジュール(nicoliveDom.js)で 2026 年 6 月に投入された「受理ゲートの純粋関数への正本化」と、その緩和を実機検証で見送った判断を、2026 年 6 月 18 日付で公開するものである。

設計は「番号という偶発マーカーではなく身元+本文という本質で受理する」「誤検知ガードはサイト供給の構造(protobuf oneof 相当)で行う」「緩和の前に実機で前提を検証する」という再利用可能な原則にまとめられる。先行技術として protocol buffers の oneof 分岐・セマンティック HTML / ARIA・サニタイザのホワイトリスト思想を参照した。

参考実装は MIT、本記事文章は CC BY 4.0 のもとで自由に複製・引用可。

引用例: 君斗りんく「消えた DOM マーカーに依存しないコメント収穫 ― 『番号』ではなく『身元+本文』で受理する構造ゲート」君斗りんくの追憶のきらめき、2026 年 6 月 18 日公開、https://tsuioku-no-kirameki.com/articles/dom-comment-harvest-identity-gate.html