Project 5
Vinmo
Web Service
JavaScript • AI

Vinmo
If you can’t find it here, it may not be online
Yahoo!ニュースのコメント欄には、攻撃的なコメント、炎上を狙ったコメント、内容の薄いコメントなど、記事を読むうえで必ずしも必要ではないノイズが大量に流れている。
一方で、コメント欄そのものをすべて非表示にしたいわけではない。
そこで考えたのが、コミュニティによる評価を利用して、一定の基準を満たさないコメントだけを自動的に折りたたむという方法だった。
「コメント欄は読みたい。でも、目にしたくないコメントまで毎回読む必要はない。」
そんな単純な違和感から、このChrome拡張機能の開発を始めた。
Yahoo!Newsコメント欄の低品質コメントを自動非表示。読むに値しない、目にもしたくない、ノイズレベルのコメントを視界から消し去ります。
目に入ると読んでしまう。意図せず不快な気持ちにさせられる。そう感じてる感受性の強い方のために。 もう全部を読まなくていい。もう無駄に疲れない。 ユーザー大衆知で判断した、クリーンでマトモなコメントだけを表示し、あなたの心を守ります。
Issue
ヤフコメのクソコメを自動非表示しUX改善
Overview
非表示基準を製作側の価値観やAIの判断ではなく、大衆知での判断ロジックを採用。 UIデザインは快適よりも少し違和感を残し有料版への足掛かり導線の1つに。 サービス名は悪ノリ重視。ただし解り易いようchrome extensions内で名称が1行で入るように。

Project Overview
クソコメ☆ブロッカーは、Yahoo!ニュースのコメント欄を対象としたChrome拡張機能。
コメントに付けられた「共感した」「なるほど」「うーん」の評価数を読み取り、あらかじめ設定した条件に該当するコメントを自動的に非表示にする。
完全にコメントを消してしまうのではなく、必要であれば展開して確認できるようにすることで、フィルタリングと閲覧性のバランスを取った。
Initial Requirements
- Chrome Extensionとして動作
- Yahoo!ニュースに対応
- ページ表示後に自動実行
- コメントの評価数を利用して判定
- DOM操作を中心とした軽量な実装
- 非表示コメントは必要に応じて展開可能
Blocking Rules
以下のいずれかに該当するコメントを非表示とした。
- 「共感した」「なるほど」「うーん」の総投票数が10件未満
- 「うーん」が「共感した」+「なるほど」を上回る
- 「うーん」が全投票数の50%以上
- 評価が0票のコメント
非表示にしたことが分かるよう、小さな表示を残し、トグル操作によってコメント本文を再表示できるUIとした。
Development Timeline
Phase 01 — Idea & Planning
最初に着目したのは、Yahoo!ニュースのコメント欄にある「ノイズ」の多さだった。
コメント欄を丸ごと消すのではなく、ユーザー自身が読む価値があると感じるコメントを残しながら、評価の低いコメントだけを視界から外す。
そのために、個別のコメント内容をAIなどで解析するのではなく、すでにYahoo!ニュース上で公開されているコミュニティ評価を利用することにした。
これなら判定処理を比較的シンプルに保ちながら、コメントの品質をある程度機械的にフィルタリングできる。
Phase 02 — DOM Scraping
最初の実装では、Chrome ExtensionのContent ScriptからYahoo!ニュースのDOMを解析した。
取得対象は以下の情報。
- コメント本文
- 「共感した」
- 「なるほど」
- 「うーん」
コメントの評価数を取得し、判定ロジックに渡して非表示対象かどうかを判断する構成を作った。
しかし、ここから想定以上にYahoo!側のDOM構造に振り回されることになる。
Problem 01 — 変動するclass名
Yahoo!ニュースはReactベースで構築されており、DOMには sc-169yn8p-3 のような変動するclass名が使用されていた。
このようなclass名を直接セレクタとして利用すると、Yahoo!側のデザイン変更によって簡単に機能しなくなる。
最初は変動classに直接依存したが、安定性に問題があった。
そこで、コメントを識別するために「共感した」「なるほど」「うーん」といった投票ボタンのテキストを利用する方法も試した。
ただし、ページ全体を対象にテキスト検索すると、記事本文など別の要素まで誤って検出してしまう。
最終的には、コメントの構造を絞り込みながら、投票ボタンの存在を確認する方式へ進めた。
完全に変動classへの依存を排除することはできず、ここは最後まで技術的な制約として残った。
Problem 02 — 記事本文まで消えてしまう
次に発生したのが、コメントだけを消したいのに記事本文まで非表示になってしまう問題だった。
Yahoo!ニュースのDOMには、記事本文とコメントで article 要素が複数存在し、単純に article を取得すると対象範囲が広すぎた。
さらに、コメント側には li > article > article という入れ子構造が存在していた。
そこで、単純なタグ検索ではなく、コメント内部に存在する投票ボタンのコンテナまで確認して対象を絞り込んだ。
この経験から、動的なWebサービスを対象とするDOMスクレイピングでは、単純なタグやclassだけではなく、その要素が本当に目的のデータを持っているかという構造的な条件まで含めて判定することが重要だと分かった。
Problem 03 — 動的に追加されるコメント
Yahoo!ニュースのコメント欄は、ページを開いた瞬間にすべてのコメントがDOMへ存在するわけではない。
スクロールによってコメントが追加されるため、初回読み込み時に一度だけDOMを解析する方式では、新しく表示されたコメントを処理できない。
そこで MutationObserver を導入した。
DOMの変更を監視し、コメントが追加されたタイミングで再度検出処理を実行する。
さらに、MutationObserverだけでは取りこぼすケースへの対策として、一定間隔での定期スキャンも併用した。
これによって、Yahoo!ニュースの動的なコメント表示にも対応できるようになった。
Phase 03 — 判定ロジックの改善
基本的なフィルタリングが動くようになった後も、実際のYahoo!ニュース上でテストすると細かな問題が見つかった。
Problem 04 — 「1.8万」のような表記
評価数には通常の整数だけでなく、「1.8万」のような日本語特有の表記が存在する。
単純に parseInt() を使うと、
"1.8万" → 1
となってしまう。
これは「1.8万票を獲得している評価の高いコメント」が、1票しかないコメントとして扱われる重大な判定ミスにつながる。
そこで数値抽出処理を専用化し、「万」が含まれている場合は10,000倍する処理を実装した。
function parseVoteNumber(text) {
const match = text.match(/([\d,.]+)/);
let num = parseFloat(match[1].replace(/,/g, ""));
if (text.includes("万")) {
num *= 10000;
}
return Math.floor(num);
}
これにより、
1.8万 → 18000
308 → 308
として扱えるようになった。
Problem 05 — 0票コメント
もうひとつ意外な落とし穴になったのが、評価数が0のコメントだった。
当初は、
「0票 = まだ評価データが取得できていない」
と判断して処理をスキップしていた。
しかし実際には、
「誰からもまだ評価されていないコメント」
という意味である可能性があり、今回の仕様では非表示対象とすることにした。
if (totalVotes === 0) {
return true;
}
ここでは、単純に0を null として扱ってしまうと判定処理まで到達しないという別の問題も発生した。
そのため、
- 投票ボタンが存在しない
- 投票ボタンは存在するが票数が0
という2つの状態を分離して扱う必要があった。
小さな仕様変更だったが、実装上はデータ取得と判定ロジックの責務を見直すきっかけになった。
Phase 04 — v1.0 Release
最終的に、v1.0では以下の機能を実装した。
Implemented
- 親コメントの自動非表示
- 3種類の評価基準による判定
- 0票コメントの非表示
- 「万」単位の評価数への対応
- 非表示 / 表示のトグル
- 「閉じる」ボタン
- MutationObserverによる動的コメントへの対応
Technology
- Manifest V3
- Vanilla JavaScript / ES6+
- HTML5
- CSS3
- Chrome Extension API
- MutationObserver API
- Chrome Storage API
- Chrome Action API
- DOM API
- Chrome DevTools
子コメント(返信欄)への対応や、変動classへの依存を完全に排除することは、v1.0では後回しにした。
Phase 05 — Chrome Web Store
拡張機能として完成した後、Chrome Web Storeへの公開を進めた。
ここで、開発中には想定していなかった別の問題に直面した。
Chrome Web Storeでは、トレーダー(商用)アカウントとして登録すると、開発者情報として本名・住所・電話番号などの個人情報が公開される仕様だった。
当初はマネタイズも視野に入れていたためトレーダーとして登録していたが、個人情報を公開することとのトレードオフが大きかった。
そこで、
- ページを限定公開へ変更
- 非トレーダーアカウントへ変更
- 公開情報を見直す
という対応を行った。
法人化やバーチャルオフィスの利用も選択肢として検討したが、コストとの兼ね合いから採用せず、個人情報の保護を優先した。
Phase 06 — Monetization
公開後は、単に作って終わりではなく、実際にマネタイズできるかも検証した。
当初はChrome Extensionそのものに課金機能を持たせる方向も検討したが、Chrome Web Storeのアカウント要件との兼ね合いから、外部サービスを利用した投げ銭方式へ切り替えた。
利用したサービスは、
- Ko-fi
- Pixiv FANBOX
- Stripe
- PayPal
など。
popup.htmlにはサポートへの導線を追加した。
UIについては、強く寄付を求めるのではなく、
このツールが役に立ちましたか?
という程度の控えめな誘導とした。
これは、プロダクトの価値を押し付けるのではなく、実際に役に立ったユーザーが任意でサポートできる形を意識したものだった。
Release Aftermath
リリースから数週間後、少ないサンプルながらユーザー動向を確認した。
インストール数: 10
アンインストール数: 3
アクティブユーザー: 2
ページ閲覧数: 52
インプレッション: 21
コンバージョン率: 19.2%
地域:
日本: 50%
アメリカ: 50%
初期のコンバージョンは高かった一方で、継続率は20%にとどまった。
この数字を見て、単純に「コンバージョン率が高い = プロダクトが成功している」とは判断できないことも分かった。
What Went Wrong
継続率の低さについては、ひとつの仮説としてネーミングの問題を考えた。
「クソコメ☆ブロッカー」という名前はキャッチーである一方、名前だけを見るとYahoo!ニュース専用のツールであることが伝わりにくい。
説明文にはYahoo!ニュース専用であることを記載していたが、実際には説明文を細かく読まずにインストールするユーザーもいる。
そのため、タイトルに「ヤフコメ」を含める案も検討した。
ただし将来的なYouTubeコメント欄などへの展開を考えると、特定サービス名をタイトルに固定することにもデメリットがある。
最終的にはタイトル変更を保留し、説明文の冒頭とスクリーンショットで「Yahoo!専用」であることを明確に伝える方向を選択した。
サンプル数自体が少なかったため、継続率についてはそのまま静観することにした。
What I Learned
Technical
Dynamic DOM is a different problem from ordinary DOM manipulation
静的なHTMLを対象にするDOM操作と、React・Lazy Load・仮想DOM・非同期描画が組み合わさった実サービスを対象にするDOM操作では、難易度が大きく違う。
今回の開発では、MutationObserverを使った動的DOMへの対応や、DOM構造を観察しながら対象要素を絞り込む方法を経験した。
Vanilla JavaScript is enough to build a Chrome Extension
フレームワークを使わず、Vanilla JavaScriptを中心にChrome拡張機能を構築できた。
特にContent Script、Chrome Storage、Chrome Action、MutationObserverなど、ブラウザが提供するAPIを組み合わせることで、比較的軽量な構成でも実用的な拡張機能を作れることを確認できた。
Data extraction and business logic should be separated
0票問題を通して、データが存在しないことと、データの値が0であることは別の状態だと分かった。
スクレイピングでは「取得できなかった」と「取得した結果が0だった」を同じ値で表現すると、後段の判定ロジックで意図しない挙動が起きる。
小さな拡張機能でも、データ取得と判定処理を分離して考える重要性を学んだ。
Design / Product
Perfect implementation is not always the right goal
開発当初は、Yahoo!ニュースのDOM変更にも完全に耐えられる構成や、子コメントまで含めた完全なフィルタリングを目指していた。
しかし、内部APIの利用はCORSや内部IDなどの制約があり、Yahoo!側の仕様に強く依存する。
そこで、実現可能性を優先して、
- 子コメント取得をやめる
- DOMスクレイピングに絞る
- 評価数だけで判定する
- 軽量な構成でリリースする
という方向へ仕様を変更した。
すべてを完璧にするよりも、まず動くものをリリースし、実際の反応を見ながら改善する。
このプロジェクトは、その判断を実践する最初の機会になった。
A high conversion rate does not mean a finished product
初期のコンバージョン率は高かったが、継続率は低かった。
この結果から、ひとつの指標だけを見てプロダクトの良し悪しを判断することの危険性も学んだ。
Future
今後の構想として、以下を検討していた。
v1.1
- 子コメント(返信欄)への対応
v3.0
- 判定基準を変更できる設定画面
- ブロックレベルの段階設定(Level 1〜5)
- ワードブロック機能
v4.0
- YouTubeコメント欄など、他サービスへの対応
- 有料版の実現
ただし、有料版についてはChrome Web Storeのアカウント要件や個人情報公開の問題があり、実現には追加の検討が必要な状態となった。
Project Flow
Planning
↓
Requirement Definition
↓
AI-Assisted Development
↓
DOM Scraping
↓
Technical Investigation
↓
Requirement Revision
↓
Chrome Extension / Manifest V3
↓
Chrome Web Store
↓
Monetization Experiment
↓
Analytics
↓
Iteration
このプロジェクトでは、単純に「Chrome拡張を作った」というだけではなく、
企画 → 実装 → 技術的な壁 → 仕様変更 → リリース → マネタイズ → 分析
という一連のプロダクト開発サイクルを実際に経験した。
それが、このプロジェクトで得た最大の成果だった。
Timeline
Concept
見積り作成業務の効率化を構想
Development
React + TypeScriptで開発
Launch
Webアプリとして公開