全体アーキテクチャ
以下の4つの枠組みで、この拡張機能の技術的な仕組みを言語化します。
1️⃣ 「何のために」を言語化する(目的の抽象化)
A. ポップアップUI(popup.html/js)
何のために存在するのか
ユーザーがGoogle Mapsを閲覧している最中に、分析を開始するための「入り口」を用意する仕組み。
ユーザーの困り
Google Mapsの場所ページを開いても、口コミの良し悪しが一目ではわかりません。100件以上あれば尚更。手作業で星の分布を数えたり、キーワードを探したりする手間が発生します。
現実世界の比喩
飲食店の口コミ一覧表を見ているのに、「この店の強みは?弱点は?」を判断するまでに全部読まないといけない。ポップアップはその場で「分析ボタン一つで、別紙の分析レポートを自動作成してくれる秘書」です。
B. スクレイピング機能(content.js)
何のために存在するのか
Google Mapsの場所ページから、見えている画面の奥にある全ての口コミデータを自動的に取得する仕組み。
ユーザーの困り
Google Mapsの口コミは「最新順に表示」できても、全件をまとめてダウンロードできません。スクロールして「もっと見る」ボタンを何度も何度も手作業で押す必要があります。
現実世界の比喩
アマゾン倉庫の棚から、お客さんが欲しい商品を全て集めるロボット。人間が「この商品ください」と言ったら、スクロールして・箱から出して・リストにまとめて差し出す。それがcontent.js。
C. ダッシュボード表示(dashboard.html/js)
何のために存在するのか
取得した生の口コミデータを、7つの視点で自動分析し、意思決定に使える「レポート」に変換する仕組み。
ユーザーの困り
200件の口コミテキストを渡されても、「この店の本当の評判は?」という経営判断に使えません。「星4と星2の割合は?」「接客の満足度は?」といった構造化された情報が必要です。
現実世界の比喩
医者が患者の症状(口コミ)を聞いて、問診票・体温・血液検査・レントゲン・脳波などの「7つの視点」で診断し、最終的に「病名」と「治療方針」を提示する。ダッシュボードはそのカルテです。
2️⃣ データの「ビフォー・アフター」を追う(データフロー)
✅ 全体フロー図
【ビフォー:ユーザーの現在状態】
Google Mapsページ
↓
└─→ スクロール見える範囲:3~5件の口コミ
もっと見るボタン:何度も手作業
【処理】
popupアイコンクリック
↓
content.jsが起動:ページの言語を読み込む
↓
自動スクロール・「もっと見る」ボタンを自動で何度も押す
↓
全ての口コミのHTMLを抽出:星・日付・テキスト・著者名など
↓
JSON形式に正規化:機械が処理しやすい形に変換
↓
chrome.storage.localに一時保存
↓
新タブで dashboard.html を開く
【アフター:ユーザーが手にする情報】
分析ダッシュボード
├─ 平均評価:4.2 ⭐
├─ 最近3ヶ月:上昇傾向 📈
├─ 星の分布:二極化している ⚠️
├─ 頻出キーワード:「美味しい」「待たされ」
├─ 5大要素の満足度グラフ
├─ 月別投稿頻度チャート
├─ ローカルガイド比率:20%
└─ 返信率:75%(低評価への対応)
詳細フロー(ファイル別)
⚙️ popup.js のフロー
【入力】
└─ ユーザーが拡張アイコンをクリック
└─ 現在のタブURL(例:google.com/maps/place/...)
【処理】
1. URLが「Google Maps場所ページ」かどうかを判定
└─ 正規表現で検証:/google\.(com|co\.jp)\/maps\/place/
2. 正規表現マッチ → content.jsを該当タブに注入
└─ chrome.scripting.executeScript()で動的に読み込み
3. ポートを開いて content.js と通信を確立
└─ chrome.tabs.connect()で双方向パイプ作成
4. 「スクレイピング開始」ボタンをユーザーが押す
└─ port.postMessage() で content.js に命令を送信
5. content.js から「進捗」メッセージを受信
└─ プログレスバーを段階的に更新(0% → 100%)
6. content.js から「完了」メッセージを受信
└─ JSON形式の分析データ(口コミ配列など)
7. データを chrome.storage.local に保存
└─ chrome.storage.local.set({ analysisData: data })
8. 新しいタブで dashboard.html を開く
└─ chrome.tabs.create({ url: 'dashboard.html' })
【出力】
└─ dashboard.html がロードされ、分析ダッシュボード表示開始
⚙️ content.js のフロー
【入力】
└─ popup.js から port.postMessage({ action: 'startScraping', maxReviews: 50 })
【処理】
1. 「クチコミ」タブをクリック(リビューパネルを開く)
└─ aria-label属性で該当ボタンを探す
└─ element.click()で自動クリック
2. 「新しい順」に並べ替え(最新レビューから取得)
└─ ソートドロップダウン → 「新しい順」をクリック
3. リビューパネルのスクロールコンテナを特定
└─ DOM構造:.m6QErb.DxyBCb クラスを探す
4. [ループ開始] 指定件数まで、またはスクロール停止まで
a) 「もっと見る」ボタンを全て自動クリック
└─ element.click()で拡張テキストを表示
b) 現在表示中の全レビューを抽出・解析
└─ querySelector()で各要素を探す
└─ 星:.kvMYJc の aria-label から数字を抽出
└─ テキスト:.wiI7pd から全文取得
└─ 日付:.rsqaWe から「2 か月前」などを取得
└─ 著者:.d4r55 から名前を取得
└─ ローカルガイド:.RfnDt に「ローカルガイド」という文字があるか
c) 重複排除:同じ著者&同じ日時のレビューは除外
d) 進捗をポップアップに送信
└─ port.postMessage({ type: 'progress', loaded: 10, total: 50 })
e) スクロール速度制限(2秒待機)
└─ DOMが新しい要素をロードするのを待つ
f) スクロールが止まった? → カウンター+1
└─ 6回連続でスクロール停止なら終了
5. [ループ終了]
6. 場所情報(名前・住所・カテゴリ・総評点)を再度取得
└─ このタイミングで取得理由:スクレイピング中に更新されている可能性
7. 全データを JSON 形式でまとめる
{
place: { name, rating, totalReviews, address, category },
reviews: [
{ authorName, stars, date, text, isLocalGuide, photoCount, ownerResponse }
],
scrapedAt: ISO8601タイムスタンプ
}
8. 完了信号を popup.js に送信
└─ port.postMessage({ type: 'complete', data: {...} })
【出力】
└─ JSON形式の構造化データ(popup.js がこれを受け取る)
⚙️ dashboard.js のフロー
【入力】
└─ chrome.storage.local から分析データを読み込み
└─ const { analysisData } = await chrome.storage.local.get('analysisData')
【処理】
≪ステップ 1: 定量的指標の分析 ≫
reviews配列の全要素に対して:
└─ 星の合計を数える → 平均値を計算
└─ 星の分布を集計:[1つ星の件数, 2つ星...5つ星の件数]
└─ 直近3ヶ月のレビューのみ抽出 → 平均値を再計算
└─ 月ごとにグループ化 → 投稿頻度をチャート化
└─ 「星5と星1の件数」 > 「星2~4の件数」? → 二極化判定
≪ステップ 2: 定性的内容(キーワード)の分析 ≫
全レビューテキストに対して:
└─ 定義済みのポジティブキーワード配列と照らし合わせ
└─ マッチしたキーワードの出現回数をカウント
└─ 同じく、ネガティブキーワードも集計
└─ 出現順でソート(多い順)
└─ 具体的エピソード検出:
テキスト長 > 40字 かつ (著者名敬称 || 動作動詞 || 心遣い表現) → 抽出
└─ 期待値ギャップ検出:
(期待関連キーワード AND ギャップ関連キーワード)→ 抽出
≪ステップ 3: 5大要素の分析 ≫
5カテゴリ [商品, 接客, 価格, 空間, 利便性] に対して、各カテゴリごと:
└─ 該当キーワードを含むレビューを抽出
└─ そのうち、ポジティブキーワードも含む件数をカウント
└─ 満足率 = ポジティブ件数 / 該当件数 × 100%
└─ 該当レビュー内の星の平均値も計算
≪ステップ 4: 時間軸トレンド ≫
月ごと・曜日ごとのグループ化:
└─ キーの抽出:「2024-01」「平日」「土日」など
└─ 各グループの平均スコアを計算
└─ 改善検出:
「リニューアル」「新メニュー」など関連キーワードのレビューを抽出
≪ステップ 5: 投稿者属性 ≫
└─ isLocalGuide === true のレビュー数をカウント
└─ hasPhotos === true のレビュー数をカウント
└─ 各グループの平均スコアを計算(ローカルガイド vs 一般)
≪ステップ 6: 競合比較 ≫
└─ 「他の店」「比べ」などのキーワードを含むレビューを抽出
└─ 「ここだけ」「唯一」などのキーワード+高評価レビューを強み として抽出
└─ 「この辺り」などのキーワード+低評価レビューをエリア課題として抽出
≪ステップ 7: 運営対応力 ≫
└─ ownerResponse フィールドが null でない件数 / 全体 = 返信率
└─ 低評価(星1~2)のうち、返信がある件数を別途集計
└─ 返信文の最初30文字が重複 → 定型文の傾向を判定
└─ 返信サンプルとして最初の3件を抽出
【出力】
└─ 7つの分析オブジェクト:
{
quant: { avg, dist, avg3, monthly, isPolarized },
qual: { posHits, negHits, episodes, gaps },
five: [ { name, satisfaction, mentions, avgStars } × 5 ],
time: { monthlyData, dayPatterns, improvementMentions },
reviewer: { localGuideRate, photoRate, lgAvg },
comp: { compMentions, strengths, areaIssues },
owner: { rate, negRate, isTemplate, samples }
}
└─ これらを HTML にレンダリング:
└─ renderSummary() 総合サマリー表示
└─ renderQuantitative() 定量的グラフ描画
└─ ... × 7 セクション
└─ DOM に追記し、ユーザーがブラウザで閲覧可能に
3️⃣ 「なぜその順序なのか」を解き明かす(制御構造)
🔄 popup.js の実行順序と分岐
1【最初】ページ読み込み完了
└─ DOMContentLoaded イベント発火
2【判定】現在のタブ情報を取得
├─ 成功した?
│ └─ YES → 次へ
│ └─ NO → エラー表示して終了 ⛔
3【判定】タブのURLが Google Maps 形式か?
├─ マッチした(google.com/maps/place)?
│ └─ YES → ポップアップにUI表示 ✅
│ └─ NO → 「Google Mapsページを開いてください」と表示
4【準備】content.js を注入(初回のみ実行)
└─ chrome.scripting.executeScript()
└─ 既に注入済み or 失敗してもエラーにしない(二重実行防止)
5【接続】popup ↔ content 間に通信パイプを開く
└─ chrome.tabs.connect()
└─ ポート名 'gmap-scraper' で識別
6【待機】ユーザーが「分析開始」ボタンをクリックするのを待つ
└─ イベントリスナー:startBtn.addEventListener('click', ...)
7【送信】content.js に「スクレイピング開始」命令
└─ port.postMessage({ action: 'startScraping', maxReviews: 50 })
8【受け取り】content.js から「進捗」メッセージが来る度に
├─ プログレスバー更新
├─ ステータステキスト更新
└─ 待機継続
9【判定】「完了」メッセージが来た?
├─ YES → ステップ10へ
├─ NO(エラー)→ エラー表示、ボタン再有効化
10【保存】データを chrome.storage.local に保存
└─ chrome.storage.local.set({ analysisData: data })
11【遷移】新しいタブで dashboard.html を開く
└─ chrome.tabs.create({ url: 'dashboard.html' })
12【完了】ユーザーが新タブで分析結果を閲覧開始
優先順位の理由
- ❌ なぜ「ページ判定」を最初に?
Google Maps以外のページなら、以後の全ての処理が無駄になるため。「駅名を確認してから乗車券を買う」のと同じ。 - ❌ なぜ「content注入」の前に「ポート接続」をしない?
content.js が注入されていなければ、ポート接続に失敗するから。「スタッフを呼ぶ前に、相手がその場所にいるか確認する」のと同じ。 - ❌ なぜ「プログレス表示」を長時間続ける?
スクレイピングは2~5分かかるため、ユーザーに「進行中」という安心感を与えるため。反応しないアプリは、ユーザーに「フリーズしているのでは?」という不安を与える。 - ❌ なぜ「storage保存」の後に「タブ作成」?
dashboard.html が読み込まれる際、storage.local からデータを読む。そのため、データが先に保存されていないと dashboard は空のままになる。
🔄 content.js の実行順序と分岐
1【初期化】このスクリプトが既に実行済みか判定
├─ window._gmapAnalyticsInit === true ?
│ └─ YES → 以後の全処理をスキップ(二重実行防止)
│ └─ NO → フラグを立てて続行
└─ 理由:popup が複数回接続しても、スクリプトは一度だけ実行したい
2【待機】popup からの接続(port)を待つ
└─ chrome.runtime.onConnect.addListener()
└─ ポート名の確認:name === 'gmap-scraper' か?
3【受信】popup から「getPlaceInfo」メッセージ
└─ 場所名・住所・カテゴリなどを抽出して返信
4【受信】popup から「startScraping」メッセージ
├─ 【ステップ 4-1】リビュータブをクリック
│ └─ document.querySelector() で該当ボタンを探す
│ └─ なぜ? Google Maps は複数のタブ(情報、写真、クチコミなど)を持つ
│ └─ クチコミ以外のタブに口コミデータはない
│
├─ 【ステップ 4-2】「新しい順」に並べ替え
│ └─ ドロップダウンメニューを開く
│ └─ 「新しい順」オプションを選択
│ └─ なぜ? 最新の評価傾向を反映したいから。古い口コミだけでは現在の評判がわからない
│
├─ 【ステップ 4-3】スクロールコンテナを特定
│ └─ Google Maps の DOM 構造から .m6QErb.DxyBCb を探す
│ └─ なぜ? このコンテナをスクロールしないと、下の口コミは読み込まれない
│
├─ 【ステップ 4-4】[ループ開始] maxReviews に達するか、スクロール停止まで
│ │
│ ├─ 4-4-a) 「もっと見る」ボタンを全てクリック
│ │ └─ 長いテキストがある場合、ボタンが表示される
│ │ └─ クリックすると全文が表示される
│ │
│ ├─ 4-4-b) 全レビュー要素を抽出
│ │ └─ document.querySelectorAll('.jftiEf') で全口コミコンテナを取得
│ │
│ ├─ 4-4-c) 各レビューから個別情報を抽出
│ │ ├─ 星:スクリーンリーダー用の aria-label から数字を正規表現で取得
│ │ │ └─ なぜ aria-label? 画面上の視覚的な「★★★★☆」は画像の場合があり、テキスト化しにくいから
│ │ ├─ テキスト:.wiI7pd の innerText を取得
│ │ ├─ 日付:.rsqaWe から「2か月前」などの相対日付を取得
│ │ │ └─ 後で parseRelativeDate() で実日付に変換
│ │ ├─ 著者名:.d4r55 から取得
│ │ ├─ ローカルガイド:.RfnDt に「ローカルガイド」テキストがあるか判定
│ │ ├─ 写真数:.KtCyie 要素の個数を数える
│ │ └─ オーナー返信:.CDe7pd から返信テキスト&日付を取得(あれば)
│ │
│ ├─ 4-4-d) 重複排除
│ │ └─ 既に reviews 配列に同じレビューがあれば追加しない
│ │ └─ なぜ? スクロール中に同じレビューが複数回表示される可能性があるから
│ │
│ ├─ 4-4-e) 進捗を popup に報告
│ │ └─ port.postMessage({ type: 'progress', loaded: 100, total: 200 })
│ │
│ ├─ 4-4-f) 目標件数に達した? → ループ終了
│ │ └─ reviews.length >= maxReviews なら break
│ │
│ ├─ 4-4-g) スクロール速度制限
│ │ └─ 2秒待機(sleep(2000))
│ │ └─ なぜ待つ? DOMが新しい要素をロードするのに時間がかかるから。
│ │ └─ 待たずにスクロールすると、キャッシュから古いデータを読む
│ │
│ ├─ 4-4-h) スクロール位置を最下部に
│ │ └─ scrollContainer.scrollTop = scrollContainer.scrollHeight
│ │
│ ├─ 4-4-i) スクロール後の高さが変わった?
│ │ ├─ 変わった → 新しい要素が読み込まれた、ループ続行
│ │ └─ 変わらない → スタレカウント+1
│ │ └─ なぜ? スクロール停止=「もう新しい口コミがない」の合図
│ │
│ └─ 4-4-j) スタレカウント >= 6 ? → ループ終了
│ └─ 6回連続でスクロール停止なら、確実に全口コミを取得したと判定
│
├─ 【ステップ 4-5】場所情報を再度取得
│ └─ スクレイピング中に場所情報が更新されている可能性があるから
│
├─ 【ステップ 4-6】全データをまとめて JSON 化
│ └─ { place: {...}, reviews: [...], scrapedAt: ... }
│
└─ 【ステップ 4-7】popup に「完了」を通知
└─ port.postMessage({ type: 'complete', data: {...} })
5【終了】ポート接続を閉じる(自動)
└─ popup が受け取ったら、popup が dashboard.html を開く
優先順位の理由
- ❌ なぜ「二重実行防止」を最初に?
popup が複数回クリックされると、content.js が複数回実行される。その度にリスナーが増え、メモリリークや二重処理につながるから。 - ❌ なぜ「リビュータブ切り替え」を最初に、「取得」の前に?
Google Maps のデフォルトは「情報」タブ。クチコミタブを開かないと、DOM に口コミ要素がない。 - ❌ なぜ「並べ替え」をすぐ?
古い順で取得して分析すると、「3年前の悪い評判に基づいた分析」になる。経営判断に使えない。 - ❌ なぜ「sleep 2秒」を入れる?
Google Maps は遅延読み込み。スクロールしてから2秒かけて新要素をレンダリングする。待たずに読むと、スクロール前の古いDOM を読む。 - ❌ なぜ「重複排除」をループ内でする?
スクロール中に、一度表示されたレビューが画面上部に戻ってくることがある。排除しないと、同じレビューが何度も集計されて、統計が歪む。 - ❌ なぜ「スタレカウント 6回」の判定?
1度目は偽陽性(たまたまロード中)の可能性があるから、複数回確認してから終了判定。これが「6回」の理由は、経験的な閾値。
🔄 dashboard.js の分析の順序と理由
1【最初】データ読み込み
└─ chrome.storage.local から analysisData を取得
└─ なぜここで? 分析に必要な全データが揃うのはここで初めて
2【定量的指標】最初に計算
├─ 理由:以後の全分析の「ベースラインスコア」になるから
├─ 平均スコア、星の分布、二極化判定
└─ これらがないと、他の分析結果の「良い/悪い」が判断できない
3【定性的分析】次に実行
├─ 理由:テキストマイニングは計算コストが高いから、早期に実行
├─ キーワード集計、エピソード抽出
└─ その後の「5大要素」分析で、このキーワード情報を再利用
4【5大要素分析】定性の後に実行
├─ 理由:「商品」「接客」などのカテゴリキーワードを確定させてから
│ 各カテゴリの満足率を計算したいから
└─ 定性分析で抽出したキーワードを「どのカテゴリに属するか」で分類
5【時間軸分析】中盤で実行
├─ 理由:月ごと・曜日ごとのグループ化に時間がかかるから
│ 計算重たい処理は早めに始めたい
└─ また「時系列グラフ」を描画するには、複数月のデータが必要
6【投稿者属性分析】相対的に軽い処理だから後回し
├─ 理由:isLocalGuide フラグをカウントするだけ、計算が簡単
└─ しかし他の分析より優先度は低い
7【競合比較】やや軽い処理だから後回し
├─ 理由:「他の店」「比べ」などのキーワードマッチングだけ
└─ 定性分析で既に全テキストを読み込んでいるので、追加コスト少なめ
8【運営対応力】最後に分析
├─ 理由:最も優先度低い
├─ 返信率の集計も、定型文判定も、計算が軽い
└─ これはオマケ情報なので、最後でいい
9【レンダリング】分析の後に一括実行
└─ 全ての分析結果が揃ってから、HTML描画を開始
└─ なぜ最後? 分析中に DOM を触ると、再レンダリングでブラウザが重くなるから
4️⃣ 依存関係を「家系図」にする(コンポーネント構成)
📦 外部依存関係
Chrome拡張機能(Gmap口コミAnalytics)
│
├─ 【親】 Google Chrome ブラウザ
│ ├─ chrome.tabs API
│ │ └─ tabs.query() : 現在のタブ情報取得
│ │ └─ tabs.connect() : content.js との通信ポート確立
│ │ └─ tabs.create() : 新しいタブ作成
│ │
│ ├─ chrome.scripting API
│ │ └─ executeScript() : content.js をターゲットタブに注入
│ │
│ ├─ chrome.runtime API
│ │ └─ onConnect : ポート接続リスナー
│ │ └─ sendMessage, postMessage : IPC通信
│ │
│ ├─ chrome.storage API
│ │ └─ storage.local.get/set() : 拡張機能のローカルストレージ
│ │
│ └─ DOM API(全てのスクリプトで利用)
│ ├─ document.querySelector/querySelectorAll
│ ├─ element.click()
│ ├─ element.textContent / innerHTML
│ └─ ... etc.
│
├─ 【親】 Google Maps ウェブサイト
│ ├─ HTML/CSS/JavaScript(Google Maps のページ構造)
│ │ └─ DOM構造 : .m6QErb, .jftiEf, .wiI7pd などのクラス名
│ │ └─ これらは Google が制御していて、更新時に変わる可能性あり
│ │ └─ 👉 **脆弱性:拡張機能の保守が必要になる可能性**
│ │
│ └─ 非同期読み込み機構(Lazy Loading)
│ └─ スクロール時に段階的にレビューをロード
│ └─ content.js の「sleep 2秒待機」はこれに対応するため
🏗️ 内部コンポーネント構成
ユーザー
│
└─ [popup.html 画面]
│ (UI:ボタン、スライダー、プログレスバー)
│
├─ popup.js (ロジック層)
│ ├─ 機能①:Google Maps 判定
│ │ └─ chrome.tabs.query() を呼び出す(Chrome API の子)
│ │
│ ├─ 機能②:content.js 注入
│ │ └─ chrome.scripting.executeScript() を呼び出す(Chrome API の子)
│ │
│ ├─ 機能③:ポート接続・通信管理
│ │ └─ chrome.tabs.connect() で content.js と繋ぐ(Chrome API の子)
│ │ └─ port.postMessage() で送信(Chrome API の子)
│ │ └─ port.onMessage.addListener() で受信(Chrome API の子)
│ │
│ └─ 機能④:ダッシュボード起動
│ └─ chrome.tabs.create() で新タブ作成(Chrome API の子)
│ └─ chrome.storage.local.set() でデータ保存(Chrome API の子)
│
└─ popup.css (見た目)
└─ グラデーション、カード、ボタンスタイル
─────────────────────────────────────
[Google Maps ページ]
│
└─ content.js (ページ内で実行される)
│
├─ 機能①:Google Maps の DOM 解析
│ └─ document.querySelector() で要素取得
│ └─ 依存:Google Maps の HTML 構造
│ 👉 脆弱性:Google が DOM 構造を変更すると動作しなくなる
│
├─ 機能②:自動操作(クリック・スクロール)
│ ├─ element.click() でボタンクリック
│ └─ scrollContainer.scrollTop でスクロール
│
├─ 機能③:データ抽出
│ ├─ element.textContent でテキスト取得
│ ├─ element.getAttribute() で属性値取得
│ └─ 正規表現で日付・数字を抽出
│
├─ 機能④:ポート通信(popup.js との双方向)
│ ├─ chrome.runtime.onConnect() でリスナー
│ ├─ port.postMessage() で送信(進捗・完了報告)
│ └─ port.onMessage() で受信(命令待機)
│
└─ 機能⑤:タイミング制御
└─ sleep() 関数で遅延読み込み待機
─────────────────────────────────────
[ダッシュボード]
│
├─ dashboard.html (UI 骨組み)
│ └─ <section> で 7つ の分析エリアを定義
│
├─ dashboard.css (見た目)
│ ├─ グリッドレイアウト、カード、グラフ(CSS棒グラフ)
│ └─ カラースキーム(Google 風のブルー・グリーン・イエロー)
│
└─ dashboard.js (ロジック+レンダリング)
│
├─ 機能①:データ読み込み
│ └─ chrome.storage.local.get('analysisData')(Chrome API の子)
│
├─ 機能②:7つの分析エンジン
│ ├─ analyzeQuantitative() : 平均・分布・トレンド
│ ├─ analyzeQualitative() : キーワード抽出
│ ├─ analyzeFiveElements() : 5カテゴリ満足度
│ ├─ analyzeTimeTrends() : 月別・曜日別分析
│ ├─ analyzeReviewerAttributes() : ローカルガイド・写真比率
│ ├─ analyzeCompetitive() : 競合比較言及
│ └─ analyzeOwnerResponse() : 返信率・質
│
├─ 機能③:キーワード辞書(内製)
│ └─ KW オブジェクト
│ ├─ KW.positive : 100+ ポジティブワード
│ ├─ KW.negative : 100+ ネガティブワード
│ ├─ KW.product, KW.hospitality, ... : カテゴリ別キーワード
│ └─ 👉 メンテナンス:言葉は時代で変わるため、定期更新が必要
│
├─ 機能④:HTML レンダリング
│ ├─ renderSummary() : サマリーカード
│ ├─ renderQuantitative() : 定量的セクション
│ ├─ ... × 7 : 各セクション用の描画関数
│ └─ DOM に innerHTML で追記
│
└─ 機能⑤:ユーティリティ関数
├─ countKeywords() : テキスト内のキーワード出現回数
├─ sortedEntries() : オブジェクトをソート
├─ escHtml() : HTML エスケープ(XSS対策)
└─ parseRelativeDate() : 「2か月前」を実日付に変換
─────────────────────────────────────
【データフロー】自分で完結している部分 vs 外部に頼っている部分
自分で完結:
• キーワード抽出ロジック(KW辞書 + マッチング)
• 5大要素の満足度算出
• 統計計算(平均・分布・トレンド)
• HTML 描画(素の JavaScript + CSS)
外部に頼っている(脆弱性):
✅ Chrome API :
└─ 将来の Chrome バージョンで API が廃止される可能性
└─ Manifest V3 への対応が必要(Manifest V2 は廃止予定)
✅ Google Maps の DOM 構造 :
└─ 【最大のリスク】
└─ Google が UI をアップデートすると、セレクタが変わる
└─ 例:.jftiEf → .review-container に変更されたら、content.js は動作しなくなる
└─ 対策:複数セレクタの fallback を用意(現在のコードは一部対応)
✅ 言語(日本語) :
└─ キーワード辞書が日本語の時流に追従する必要あり
└─ 新しい俗語が出ると、取りこぼしが発生
└─ 例:「ぴえん」「推し活」などの新語への対応
📊 総括:依存関係マップ
最強固い(変わらない)
↑
│ ┌─── 標準的な DOM API(querySelector など)
│ │ (W3C 標準だから、ブラウザが廃止しない)
│ │
│ ├─── 標準的な JavaScript(正規表現、配列操作など)
│ │ (ECMAScript 仕様に従っている)
│ │
│ └─── 自作のキーワード辞書
│ (コントローラブルだが、メンテナンスコスト)
│
├─── Chrome 拡張 API(tabs, runtime, storage)
│ (比較的安定、Manifest V3 対応が必須)
│
└─── Google Maps の DOM 構造
(最も脆弱性が高い)
(Google が予告なく変更する可能性)
最も脆い(変わりやすい)
↓
🎯 まとめ
| 観点 | 要点 |
|---|---|
| 1. 何のために | ユーザーが 1クリック で、Google Maps の全口コミを自動スクレイピング&7観点の分析レポートを生成する「秘書」 |
| 2. データフロー | Google Maps(見える範囲)→ 自動スクロール取得 → JSON正規化 → 7つの分析エンジン → HTML可視化 |
| 3. 実行順序 | 最初は「前提条件チェック」→「低コスト処理」→「計算重い処理」→「UI描画」の順で効率化 |
| 4. 依存関係 | Chrome API 依存(堅牢)← → Google Maps DOM 依存(脆弱) |