はじめに
オプチャグラフは、LINE OpenChatの統計情報を収集・分析・可視化するWebサービスです。
前回までの記事:
- ①データパイプライン - 25万件/毎時のクロール〜静的ファイル生成
- ②差分検出 - 25万件の更新を99%削減する仕組み
- ③バッチ設計 - 冪等性・再開可能性・障害耐性
- ④2025年の技術的チャレンジ振り返り - 多言語対応の実装・キーワードスパム対策等
- ⑤タグ機能 - オープンチャットを分類するタグ付けシステム
1. 背景:20年前の手法が繰り返されている
キーワードスタッフィングとは
「キーワードスタッフィング(Keyword Stuffing)」とは、検索結果の上位表示を狙って、ページ内に大量のキーワードを詰め込む手法です。
Googleの公式スパムポリシーでは、以下のようなパターンが該当するとされています:
- 同じキーワードを不自然に何度も繰り返す
- 都市名・地域名のリストを羅列する
- 関連キーワードを文脈なく大量に並べる
【同じキーワードの繰り返し】
安い靴をお探しですか?当店の安い靴は最も安い靴です。
安い靴を買うなら安い靴の専門店で安い靴をお買い求めください。
【地域名・関連キーワードの羅列】
石狩 札幌 かみのやま 富谷 野々市 射水 入間 新三郷 つくば 前橋
30代 40代 50代 60代 主婦 一人暮らし 節約 ポイ活 お得
Googleでの歴史:すでに「終わった」手法
キーワードスタッフィングは、1990年代後半〜2000年代初頭のSEO黎明期には実際に効果がありました。当時の検索エンジンは単純で、キーワードの出現回数が多いほど、またより多くのキーワードを含むほど上位に表示されていたのです。
しかし、Googleは段階的にこの手法を無効化してきました:
| 年 | アップデート | 内容 |
|---|---|---|
| 2003年 | Florida | キーワードスタッフィングへの最初の本格的対策 |
| 2011年 | Panda | 低品質コンテンツ・スパムサイトの排除 |
| 2013年 | Hummingbird | 自然言語処理の導入、文脈理解の向上 |
| 2019年 | BERT | 高度な自然言語処理により文脈・意図を理解 |
現在、キーワードスタッフィングはGoogleの「スパムに関するポリシー」で明確に禁止されており、発覚すると検索順位の低下やインデックスからの完全削除といったペナルティを受けます。
SEOの世界では20年以上前に「終わった」手法なのです。
オープンチャットで繰り返される歴史
ところが、LINEオープンチャットの説明文では、この古典的なスパム手法が今も横行しています。
オープンチャットにはLINE公式の検索機能があり、説明文に含まれるキーワードで検索できます。そこで一部のユーザーは、検索でヒットさせるために関連・無関連を問わず大量のキーワードを詰め込むのです。
検索アルゴリズムが単純な環境では、20年前と同じ問題が繰り返される ── これがオプチャグラフでキーワードスタッフィング対策を実装した背景です。
2. 対策の概要
実際の例
コストコオプチャ最大規模のメンバーです!
コストコのあれこれ、㊙️情報まで盛りだくさん✨
よろしくお願い致します🙇♀️
コストコ costco Costco 主婦 一人暮らし 節約 ポイ活 お得 ブランド お買い得
爆買い 大家族 フードファイター 矢田亜希子 ジャンクSPORTS 業務スーパー KALDI
石狩、札幌、かみのやま 富谷 野々市 射水 入間 新三郷 つくば 前橋 ひたちなか
30代 40代 50代 60代 20代 きゅん スティッチ
前半は普通の説明文ですが、後半は明らかに検索用のキーワードを詰め込んでいます。
このようなスパムを放置すると、以下の問題が発生します:
- サイト内検索の品質低下:検索結果がスパム的なオープンチャットで埋まってしまう
- GoogleのSEOスパム判定リスク:キーワードスタッフィングはGoogleのスパムポリシー違反であり、サイト全体の評価が下がる恐れがある
- ユーザー体験の低下:説明文が読みにくくなる
なぜ機械学習ではなく正規表現か?
- 月額1,000円のサーバーで25万件を処理する制約
- パターンの調整がしやすい(新しいスパムに即対応可能)
- 判定理由を説明できる
📝 初期コミット: 763456cb
🔗 関連リンク
| リソース | URL |
|---|---|
| 実装コード | CollapseKeywordEnumerations.php |
| テストコード | CollapseKeywordEnumerationsTest.php |
3. 日本語の「キーワードらしさ」を判定する
3.1 ひらがな率による判定
日本語の特徴として、キーワード(名詞)はひらがなが少なく、文章はひらがなが多いという傾向があります。
| テキスト | ひらがな率 | 判定 |
|---|---|---|
カフェ |
0% | キーワード ✅ |
マッキンゼー |
0% | キーワード ✅ |
Bitcoin |
0% | キーワード ✅ |
これは |
100% | 文章の一部 ❌ |
食べました |
60% | 文章の一部 ❌ |
についての |
80% | 文章の一部 ❌ |
この法則を使い、ひらがな率30%以下をキーワードと判定します。
$hiragana = preg_match_all('/\p{Hiragana}/u', $t);
return ($hiragana / max(1, $letters)) <= 0.3;
🔗 コード: isKeywordLike() L316-L384
3.2 英語への対応
英語の場合は別のルールが必要です:
| パターン | 判定 | 理由 |
|---|---|---|
the, and, for
|
文章 ❌ | 一般的な英単語 |
Apple, Microsoft
|
キーワード ✅ | 大文字を含む(企業名) |
Ernst & Young |
キーワード ✅ |
&記号を含む |
test, word, long
|
文章 ❌ | 4文字以上の小文字のみ |
3.3 具体例:スペース・読点区切りのキーワード羅列
この判定を使って、以下のようなパターンを検出・削除します。
【入力】
コストコ costco Costco 主婦 一人暮らし 節約 ポイ活 お得 ブランド お買い得
爆買い 大家族 フードファイター 矢田亜希子 ジャンクSPORTS 業務スーパー KALDI
【出力】
(削除される)
【入力】
石狩、札幌、かみのやま、富谷、野々市、射水、入間、新三郷、つくば、前橋、
ひたちなか、千葉ニュータウン、幕張、木更津、多摩境、金沢シーサイド
【出力】
(削除される)
判定理由: 各トークンがひらがな率30%以下(カタカナ・漢字・英語)で、12個以上並んでいる
4. 文章とキーワード羅列を見分ける
4.1 助詞の存在をチェック
日本語の文章には必ず助詞(が、を、に、へ、で、と、や...)が含まれます。
$particleMatches = preg_match_all('/[がをにへでとやからまで]/u', $matchedText);
🔗 コード: isSentenceLike() L386-L441
4.2 読点・縦棒の数と助詞の比率
判定ルール:
| 条件 | 判定 |
|---|---|
| 読点5個以上 かつ 助詞が読点の半分以下 | キーワード羅列 |
| 縦棒5個以上 かつ 助詞が縦棒の1/3以下 | キーワード羅列 |
| 100文字以上 かつ スペース10個以上 かつ 助詞密度0.02未満 | キーワード羅列 |
| 上記以外で助詞が含まれる | 文章(保持) |
📝 コミット: 3f9ae1d7 - スペース区切りの検出を追加
4.3 具体例:利用ルール vs 企業名羅列
縦棒区切りでも、文章として意味があるものは保持します。
【入力:利用ルール】
敬語で会話すること|建設的な議論を行うこと|情報共有を抑制する発言は禁止|
秘密保持を求められた場合はその事実のみ共有すること
【出力】
敬語で会話すること|建設的な議論を行うこと|情報共有を抑制する発言は禁止|
秘密保持を求められた場合はその事実のみ共有すること
(そのまま保持される)
判定理由: 「で」「を」「の」などの助詞が多く含まれる → 文章と判定
【入力:企業名羅列】
コンサル/シンクタンク|マッキンゼー|BCG|ベイン|ATカーニー|PwC|
Deloitte デロイト|KPMG|EY|アクセンチュア|IBM|NRI 野村総合研究所
【出力】
(削除される)
判定理由: 助詞がほとんどなく、各トークンがキーワード的 → キーワード羅列と判定
5. ハッシュタグの重複削除
5.1 本文と重複するハッシュタグは削除
【入力】
これはコストコの情報共有グループです #コストコ #お買い得 #節約
【出力】
これはコストコの情報共有グループです #お買い得 #節約
判定理由: #コストコ は本文に「コストコ」が含まれるため冗長 → 削除。他は本文に含まれないため保持。
6. 埋め込み型キーワード羅列への対応
6.1 問題:文章の中にキーワード羅列がある
単純なキーワード羅列だけでなく、文章の途中に埋め込まれたキーワード羅列もあります。
【入力】
ビットコイン、NFT、チリーズ、SAND、ENJIN、リップル、ネム、IOST、ステラ、
イーサリアム、エイダ、トロン、バージ、ポルカドット、モナコイン、ライトコイン、
リスク、オミセゴー、ファクトム、バイナンスコイン、テザー、ビットコインキャッシュ、
モネロ、テゾス、VeChain、BAT、QTUM、LINK、EOS、NEO、DOGE、IOTA、DASH、AVAX、
FIL、NANOなどのアルトコインから草コインまでの仮想通貨のチャートを予想するグループです。
この場合、「〜などの」という接続部分を検出して処理します。
6.2 「など」を目印にする
// 「など」「から」「まで」「といった」の直前でマッチ
$embeddedPattern = '/([^。!?\n\r]*[、,,][ ]*[^、,,。!?;:\n\r]+
(?:[、,,][ ]*[^、,,。!?;:\n\r]+){' . $embeddedMinCount . ',})
(?=など|から|まで|といった|について)/u';
📝 コミット: c440a66d - 埋め込み型処理を追加
6.3 keepFirst パラメータ:先頭N個を残す
埋め込み型の場合、全削除すると文章が不自然になります。そこで keepFirst パラメータで先頭N個を残し、残りを「etc…」に省略できます。
【入力】
ビットコイン、NFT、チリーズ、SAND、ENJIN、リップル...NANOなどの
アルトコインから草コインまでの仮想通貨のチャートを予想するグループです。
【出力(keepFirst=1の場合)】
ビットコイン、etc…などのアルトコインから草コインまでの
仮想通貨のチャートを予想するグループです。
【出力(keepFirst=0の場合)】
などのアルトコインから草コインまでの
仮想通貨のチャートを予想するグループです。
7. 正規表現のエラーハンドリング
7.1 PHPの正規表現関数の落とし穴
PHPの preg_* 関数は、エラー時に例外を投げずに特殊な値を返すという罠があります。
| 関数 | 正常時 | エラー時 |
|---|---|---|
preg_replace() |
置換後の文字列 | null |
preg_split() |
配列 | false |
preg_match_all() |
マッチ数 | false |
これを見逃すと、本番環境で予期しないエラーが発生します。
7.2 対策
// 対策1: 戻り値のnullチェック
$content = preg_replace('/[##][^\s##]+/u', '', $text);
if ($content === null) {
$content = $text; // エラー時は元の文字列を使用
}
// 対策2: パラメータの範囲制限
if ($minItems < 1) $minItems = 1;
if ($minItems > 10000) $minItems = 10000;
// 対策3: エラーハンドラーでWarningをキャッチ
set_error_handler(function () {
return true; // Warningを無視
}, E_WARNING);
// ...処理...
restore_error_handler();
🔗 コード: エラーハンドリング L109-L293
📝 コミット: 83b31144 - エラーハンドリング強化
8. まとめ
| 日時 | コミット | 内容 |
|---|---|---|
| 8/23 16:09 | 763456cb | 初期実装(ひらがな率判定、ハッシュタグ処理) |
| 8/23 17:17 | 83b31144 | エラーハンドリング強化 |
| 8/23 | 3f9ae1d7 | 複数段落処理、スペース区切り検出 |
| 8/23 18:33 | c440a66d | 埋め込み型キーワード羅列(etc…)対応 |
| 8/24 17:26 | cbeab961 | インターフェース追加、DI対応 |
機械学習を使わない選択のメリット:
- ✅ 低コスト:追加のインフラ不要
- ✅ 説明可能:なぜその判定になったか追跡できる
- ✅ 調整しやすい:新しいスパムパターンに即対応可能