ユーザーがアドブロックを有効化しているかどうか検知する方法

   · ☕ 5 min read

ブラウザの拡張機能であるアドブロックを有効化した状態だと、特定のサイト(The New York Timesなど)に訪れた際にアラートが表示されることがある。

この検出の仕組みが気になったので調べてみた。

TL;DL

アドブロックの検出はとても簡単に実装できる。

実際にデモとして検出を行っているものはこちら:https://brave-joliot-eb62ed.netlify.app/

ソースコードはこちら: https://github.com/benibana2001/ads-blocker-detect/tree/master/src

前提として、一般的にアドブロックの実装は、内部に保持したブラックリストを使用することで機能を実現している。そのリストに含まれる名前に合致するファイルの読み込みを試行した際、広告取得が実行されたと判定を行っている。

例えば、htmlファイル内に <script src="ads-video.js"></script> のような記述があった際、もし仮にブラックリストにads-videoという文字列が含まれているならば、このスクリプトの読み込みを広告表示のためのものとみなして読み込み自体をブロックする。このような形でアドブロックは実装されている。

そのため開発者側(広告を埋め込みたい側)はこの機能を逆手に取り、故意にリストに引っかかるような名称のファイル(例えば ads.js)をダミーとしてあらかじめ作成しておくことで、ユーザーを出し抜くことができる。
この場合であれば、ads.jsというファイルの読み込みに成功したか否かをJavaScriptで判定するスクリプトを書いておくことで、ユーザーがアドブロックを有効化しているか否かを特定することができる。

上記の手法は、コードも含めてADBLOCK ANALITYCSという企業がこちらで説明しているものを要約したものである。そのため、そちらを読んでいただいただければこの記事に目を通す必要はない。

ads.js ファイルを用意する

以下、デモとして作成したコードの詳細な説明。

今回はアドブロック検出用のダミーコードとして、下記のようなファイルを用意した。

1
const detect = () => console.log("detect");

多くのアドブロック機能においては、ad~という文字列を含んだ(いかにもそれっぽい)ファイル名は広告スクリプトとしてブラックリストに定義されている可能性が高いため、ダミーファイルの名前はads.jsのようなファイル名にしておく。

そしてこのファイルを、htmlから直接読み込む。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<body>
    <!-- ダミーファイルの読み込み -->
    <script type="text/javascript" src="ads.js"></script>
    <script>
      // 確認用メッセージ
      const message = {
        off: "お使いのブラウザのアドブロックは無効になっているようです。",
        on: "お使いのブラウザのアドブロックは有効になっているようです。",
      };

      // ダミーファイルの読み込み成否判定をtypeof を使用して確認
      const isWorkingBlocker = () => typeof detect !== "function";

      if (isWorkingBlocker()) {
        alert(message.on);
      } else {
        alert(message.off);
      }
    </script>

もしads.jsファイルの読み込みに成功していれば、定義した関数detectが名前解決される。この解決の成否により、ユーザーのアドブロックの状態を推定することが可能となる。

注意点

webpackなどのバンドルツールを使用した場合は、main.jsといった形の一つのファイルにバンドルされるため、当然ながらこの手法は使用できない(追記あり)。webpack html plugin を使用し、テンプレートとしてこの機能を事前に埋め込んでおくとともに、ads.jsファイルのみバンドルから除外されるように設定しておく必要がある。

おわりに

上記の方法により、一般的なアドブロックソフトの機能を回避できる。OSSとして公開されているsitexw/FuckAdBlock(Githubスター数1,700)も、ざっとみた感じでは類似の実装であるため、多くの開発者によってこのトリックは使用されていると予想される。

ただし、その他の手法でブロックを実行している拡張ソフトの存在もありえる。しかし、特定のプログラムの実行を阻害するためには、ファイル自体の読み込みをブロックするという手法は理にかなっており、それ以外の手法は考えにくい。何かしら読み込みを阻害するスクリプトが検出されるのであれば、そこには高い確率でアドブロックを実行している可能性がある。開発者としては検出に引っかかった瞬間にページの読み込みをストップして回避する手段が存在するため、必要に応じて使用すればよく、それ以上の深い対策方法を模索する必要はないと考える。

追記

webpack上でエントリーポイントを複数に設定することで、webpackでもバンドルファイルを複数に調整可能だと教えて頂いた。これにより、テンプレートとして事前にhtmlファイル内にscriptタグを埋め込んでおく必要はないことが分かった。

バンドルファイルを複数に分けて読み込むために、webpack.config.jsを以下のように調整。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  entry: {
    ads: "./src/ads.js",
    app: "./src/index.js",
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/html/index.html"
    }),
  ],

  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "dist"),
  }

上記の設定により、entryに記載した順番通りに、別個のファイルとしてhtmlファイル内に各JSファイルを参照するscriptタグが挿入される。そのため今回は、ads.jsをメインのindex.jsよりも上に書くことで、ads.jsが先に読み込まれるよう考慮した。

また今回の実装では、グローバルに関数が存在するか否かを判定することで広告の挿入を検出していた。しかしwebpackを導入することでこのトリックが使用できなくなるため(ファイル別に名前空間が切られる)、新たに以下のようなトリックを仕込んだ。

  1. ads.jsでダミーのDOM要素を追加する
  2. index.jsで、ダミーのDOM要素を取得できるか試行する
  3. 2の試行によりDOMがキャッチできればアドブロックは無効、キャッチできなければ有効となっていると推測できる。

この手法は、前述のADBLOCK ANALITYCSがこちらで実装している方法だ。

参考: https://webpack.js.org/concepts/output/#multiple-entry-points

Share on

whasse
WRITTEN BY
whasse
Web Developer