はじめに
こんにちは、A-betのtoruです。個人でMarTech Inspectorというツール検知系のChrome拡張機能を開発・公開しています。
このツールは、閲覧中のWebサイトがGA4、GTM、Adobe Analytics、Tealium、KARTEなど、24種類のMarTech/計測ツールのうちどれを使っているかを自動で判定し、一覧表示するというものです。
「サイトが何のツールを使っているか」を判定するのに、各ツールベンダーが公式APIを提供しているわけではありません。そこで使うのが、いわゆるfingerprint(フィンガープリント)方式での検知です。この記事では、その実装レベルの考え方を紹介します。
MarTech Inspectorの実際の検知ルール(24ツール分の具体的なパターン)自体は非公開にしていますが、「どういう発想で・どういう技術要素を組み合わせているか」は共有できる範囲でまとめます。
そもそも何を手がかりに検知するか
計測ツールは大きく分けて、ページ上に痕跡を残します。手がかりになるのは主に3種類です。
-
グローバル変数(
windowオブジェクトに生えるオブジェクト・関数) - Cookie(命名規則にツールごとの特徴が出る)
- リクエスト先のドメイン・パス(計測ビーコンの送信先)
これらを単独で見るのではなく、複数組み合わせて「確度」を上げるのがポイントです。単一の手がかりだけだと誤検知が起きやすいためです。
1. グローバル変数を見る
多くの計測ツールは、初期化時にwindowオブジェクトへ何らかのプロパティを生やします。たとえばGTMであればwindow.dataLayer、GA4であればwindow.gtagが代表的です(これは各ツールの公式ドキュメントに書かれている公開情報です)。
// 概念的な例:グローバル変数の有無をチェックする
function checkGlobalVars() {
const signals = [];
if (typeof window.dataLayer !== 'undefined') {
signals.push({ tool: 'GTM/GA4系', signal: 'dataLayer', confidence: 0.6 });
}
if (typeof window.gtag === 'function') {
signals.push({ tool: 'GA4', signal: 'gtag', confidence: 0.6 });
}
return signals;
}
ここで注意が必要なのは、MV3のcontent scriptはデフォルトでページ本体とは別のJS実行コンテキスト(isolated world)で動くため、ページが持つwindow.dataLayerを直接読みには行けないという点です。
これを回避するために、chrome.scripting.executeScriptのworld: "MAIN"オプションを使い、document_startのタイミングでページ本体の実行コンテキストに直接コードを注入します。
chrome.scripting.executeScript({
target: { tabId },
world: "MAIN",
func: () => {
return {
hasDataLayer: typeof window.dataLayer !== 'undefined',
hasGtag: typeof window.gtag === 'function'
};
}
});
document_startで仕込むことで、ページ自身の初期化スクリプトが走るより先にフックできるため、後からwindowオブジェクトが書き換えられてしまうケースにもある程度耐えられます。
2. Cookieの命名規則を見る
計測ツールは、識別子をCookieに保存することが多く、ツールごとに命名規則の傾向があります。たとえばGA4であれば_gaから始まるCookieを発行することが公式に案内されています。
// 概念的な例:Cookie名のパターンマッチ
function checkCookiePatterns(cookies) {
const signals = [];
for (const cookie of cookies) {
if (/^_ga/.test(cookie.name)) {
signals.push({ tool: 'GA4', signal: `cookie:${cookie.name}`, confidence: 0.5 });
}
// 実際にはツールごとの正規表現パターンをテーブル化して回している
}
return signals;
}
Cookie名は難読化・変更されることもあるため、これも単独の決め手にはせず、あくまで一つの信号として扱います。
3. リクエスト先のドメイン・パスを見る
計測ビーコンは、決まったドメイン(またはパスの特徴)にリクエストを送信します。Chrome拡張機能ではchrome.webRequest(MV3では読み取り専用のonBeforeRequestなど)を使って、発生したリクエストのURLを監視できます。
chrome.webRequest.onBeforeRequest.addListener(
(details) => {
const url = new URL(details.url);
// ドメインパターンに応じてツールを推定する
matchRequestPattern(url.hostname, url.pathname);
},
{ urls: ["<all_urls>"] }
);
MV3ではwebRequestのブロッキング系機能が制限されているため、あくまで「観測」目的での利用にとどめる必要がある点は注意が必要です(通信を書き換えたり止めたりする用途には使えません)。
3種類の信号を組み合わせて確度を上げる
ここまでの3つの信号(グローバル変数・Cookie・リクエスト)は、それぞれ単独では誤検知の可能性があります。たとえば「dataLayerという変数名だけ」であれば、計測ツールと無関係な自作のJSが偶然同じ名前を使っている可能性もゼロではありません。
そこで、MarTech Inspectorでは各信号に重み(confidence)を持たせ、複数の信号が同時に揃った場合にのみ「検知済み」と判定するロジックを組んでいます。
// 概念的な例:複数信号のスコアを合算して閾値判定する
function judgeDetection(signals, threshold = 0.8) {
const scoreByTool = {};
for (const s of signals) {
scoreByTool[s.tool] = (scoreByTool[s.tool] || 0) + s.confidence;
}
return Object.entries(scoreByTool)
.filter(([, score]) => score >= threshold)
.map(([tool]) => tool);
}
実際の重み付けやパターンの組み合わせ方(24ツール分のテーブル)はプロダクトの中核ロジックなので非公開にしていますが、「単一シグナルで判定しない」という設計方針自体は、この手の検知ツールを作る上で汎用的に使える考え方だと思います。
まとめ
- MarTechツールの検知は、公式APIがない前提で「グローバル変数・Cookie・リクエスト」の3系統の手がかりを組み合わせるfingerprint方式で行う
- MV3環境では、ページ本体のグローバル変数を見るために
world: "MAIN"+document_startでの早期注入が必要 - 単一の手がかりだけで判定すると誤検知しやすいため、複数信号のスコアリングで確度を上げるのが実用上のポイント
MarTech Inspector自体はChrome Web Storeで公開しているので、実際の検知結果を見てみたい方はこちらからどうぞ。
開発の経緯や他の機能(Live Mock、A/B Calculatorなど)については、noteで連載しています。