広告ブロックをするクライアントに広告を表示させる

こんにちは！

早速ですが、みなさん！
広告ブロックされてますか？

私は利用しています！笑

しかし、あまり公ではありませんが、実際には広告ブロックを回避し、クライアントに表示する方法はあります！

## 検証環境

### PC

- macOS Catalina：`10.15.2`
- Google Chrome Canary：`81.0.4023.0`
- AdBlock(Chrome Extension)：`4.1.0`

### モバイル

- iOS：`13.3`
- Safari
- 280blocker：`4.1.1`

## 仕組み

広告ブロックを行うサードパーティ製のアプリケーションによって細かい仕組みは異なります。

あくまで、今回例に上げる

- Chrome拡張機能のAdBlock
- iOS Safariのコンテンツブロッカーである280blocker

を対象としています。

そもそも、広告ブロックを行うにはブロックをする広告を定義した情報が必要です。
その一つとして日本では、[豆腐フィルタ](http://tofukko.r.ribbon.to/abp.html)が有名です。

実際に豆腐フィルタの[定義情報](https://raw.githubusercontent.com/tofukko/filter/master/Adblock_Plus_list.txt)を確認すると、決められたフォーマットでブロックを行う広告が定義されているのを確認できることと思います。

広告ブロックを行う小さなアプリケーションでも大抵の場合、AdBlockのようなユーザーを多く抱えている広告ブロックアプリケーションのフォーマットに対応しています。

例として、Brave Browserの開発者の方がAdblockフィルターをパースするものを公開しています。

- [abp-filter-parser](https://github.com/bbondy/abp-filter-parser)

広告ブロックアプリケーションは、定義ファイルを用いてクライアントに表示されないようにしています。

###　内部仕様

広告ブロックアプリケーションによって細かな仕様も異なりますが、

1. HTTPリクエスト内にフィルタに合致するものがあれば弾く
2. DOMロード中にタグやclass、href、srcがフィルタに合致すれば、該当DOMを非表示又は削除する

が一般的です。
また今回検証したChrome拡張機能のAdBlockは、DOMロード後も常に監視しており、フィルタに該当するリクエストやDOMは即座に上記処理を走らせています。

`※内部仕様の解説が雑ですみません！`

### 対策方法

#### バナー画像

バナー画像(src)を弾かれない方法としては、

- 自サーバーに予めバナー画像を保存しておき、フィルタに弾かれないようなファイル名とする
- Base64形式にエンコードしたバナー画像を埋め込む

の2通りがあります。

フィルタに弾かれないようなファイル名はユニークなファイル名であれば問題ありません。

##### Base64にエンコードした画像を埋め込む

HTMLではBase64にエンコードを施した画像の埋め込みに対応しており、

```html
<img src="data:image/png;base64,Base64にエンコード後のバイナリデータ文字列">
```

上記の、

```
image/png
```

は該当画像のMIME Typeです。
画像の拡張子によって、MIME Typeは異なります。

- [MIME Type一覧](https://asahi-net.jp/support/guide/homepage/0017.html)

上記の例に則ってBase64にエンコードをした画像を表示する場合に、

- MIME Typeの取得
- 画像をBase64にエンコードしたバイナリデータ文字列の取得

を行うPHPのサンプルコードは下記になります。

```php
function img_file_to_mimetype_and_base64_binary_data($img_file_src)
{
    $img_file = file_get_contents($img_file_src);
    $mime_type = finfo_buffer(
        finfo_open(),
        $img_file,
        FILEINFO_MIME_TYPE
    );
    $binary_data = base64_encode($img_file);
    return ["mime_type" => $mime_type,"binary_data" => $binary_data];
}
```

上記サンプルコード中の関数の引数としている`$img_file_src`には該当画像の`絶対パス`若しくは`URL`を許容するとしています。

また、予めBase64にエンコードされたバイナリデータ文字列を把握していなくとも、出力をするバッファリングを制御すれば、アクセス時にそのまま走らせることも可能です。

- [ob_start()](https://www.php.net/manual/ja/function.ob-start.php)
- [ob_end_flush()](https://www.php.net/manual/ja/function.ob-end-flush.php)

Wordpressであれば、

```php

//出来るだけ早くフックに掛けてバッファリングを有効にする（ob_start）
add_action('registered_taxonomy','関数名');

//出来るだけ遅くフックに掛けて出力用バッファに漏れが無いようにする（ob_end_flush）
add_action('shutdown','関数名');
```

のように最初と最後にそれぞれフックを掛ければバッファ漏れが無いと思います。

##### href対策

aタグのリンク先であるhrefは、自サーバー内等でワンクッション挟んでリダイレクトをすれば特に問題はありません。

※フィルタに合致する文字列をhref内に含まないようにする必要があります

#### 広告ブロックを行うユーザもGoogle Analyticsで確認したい

大抵の広告ブロックアプリケーションはサードパーティ製のアクセス解析ツールを弾いていることがほとんどです。
そして、例の通り`gtag.js`や`analytics.js`も弾きます。
弾かれないよう自サーバー内に保存をしておいて、読み込む場合でも弾かれます。
というのも、常時HTTPリクエストも監視しているからです。
Google AnalytcisのURL文字列もフィルタによって定義されているため、jsファイル内のHTTPリクエスト自体も弾かれることになります。

なのでここではGoogle Analyticsの[Measurement Protocol](https://developers.google.com/analytics/devguides/collection/protocol/v1?hl=ja)を利用します。

```
Google アナリティクスの Measurement Protocol を使用すると、
HTTP リクエストでユーザーの利用状況に関する生データを
Google アナリティクス サーバーに直接送信できます。
これにより、ほとんどすべての環境で、
ユーザーがビジネスをどのように利用しているかを測定できます。
```

##### 広告ブロックユーザーの判定

広告ブロックユーザーを判定する方法として、よく紹介されているのは

```javascript

if(typeof ga === 'undefined')
{
}
```

のように、Google Analyticsのjsで定義する変数(ga)がundefinedか判定するものですよね。

実際には、広告ブロックを行っておらずアクセス解析のみ弾くクライアントも存在するため、

```html
<div class="target_dom pub_300x250 pub_300x250m pub_728x90 text-ad textAd text_ad text_ads text-ads text-ad-links ad-text" style="position: fixed;z-index: -1;">
    <a href="https://px.a8.net" target="_blank" rel="nofollow">
        <img border="0" width="1" height="1" alt="" src="a8.net">
    </a>
</div>
```

のように、如何にもフィルタが弾きそうなDOMを`position: fixed;`且つ`z-index: -1;`でフッターに散りばめておきます。

そして、広告ブロックしている場合でもDOMロード後に処理が走っていない場合があるため、

```javascript
setTimeout(関数名,3000);
```

とし、DOMロード後から3秒後くらいに発火させます。

発火させた関数内では、

- .target_dom
- .target_dom a
- .target_dom img

のそれぞれのDOMが

- そもそも存在するか
- もしくは非表示か

の2通り確認する必要があります。
前述しましたが、これは広告ブロックアプリケーションによって、DOMを丸ごと削除するものや非表示にするものもあるためです。

上記のDOMが、

- 存在しない
- 非表示になっている(visibility:hiddenまたはdisplay:none)

どちらかに該当する場合はクライアントで広告ブロックが走っています。

※判定後は、上記DOMを削除しておきましょう。

##### Google AnalyticsのMeasurement Protocolを利用する

上記過程で広告ブロックを確認できても、アクセス解析ツールは動くクライントである可能性もあるため、（二重送信を防ぐ）

```javascript

if(typeof ga === 'undefined')
{
}
```

念の為、Google Analyticsのjsが弾かれているか確認します。
ここで、弾かれていることを確認できれば、[Measurement Protocol](https://developers.google.com/analytics/devguides/collection/protocol/v1?hl=ja)を利用することになりますが、そのままAjaxでGoogle宛にPOSTすると、ここでもHTTPリクエストを弾かれてしまいます。

なので、自サーバー内等にAjaxでPOSTし、サーバーサイドでMeasurement Protocolを利用することとなります。

その際に最低限必要なデータは、

- 現在訪問中のページタイトル(`encodeURI($("title").text())`)
- 現在訪問中のページURL
- cid（同一ユーザーを識別するため）

です。
同一ユーザーを識別する[cid](https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters?hl=ja#cid)は`sessionStorage`に格納しておくと扱いやすいと思います。

またGoogle Analytics上で広告ブロックユーザーを別途で照会したい場合は、[キャンペーン](https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters?hl=ja#cn)を利用すると良いです。
※使用用途を間違えていることは承知です笑


## まとめ

個人的には広告ブロックの仕組みを理解した上で「HTTPリクエストの知識」をおさらいする目的だったため、かなり雑に紹介してしまいました、、、😂

あくまで検証環境のみで確認したものなので、実際に扱う場合は都度検証を行って挙動の確認をしてもらえると幸いです！

なので、こういう仕組みでこういう対策方法があるんだーっというのを頭の片隅に格納するくらいの記事です笑

ありがとうございました！