Help us understand the problem. What is going on with this article?

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

こんにちは!

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

私は利用しています!笑

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

検証環境

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

を対象としています。

そもそも、広告ブロックを行うにはブロックをする広告を定義した情報が必要です。
その一つとして日本では、豆腐フィルタが有名です。

実際に豆腐フィルタの定義情報を確認すると、決められたフォーマットでブロックを行う広告が定義されているのを確認できることと思います。

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

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

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

 内部仕様

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

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

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

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

対策方法

バナー画像

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

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

の2通りがあります。

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

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

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

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

上記の、

image/png

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

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

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

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

Wordpressであれば、

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

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

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

href対策

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

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

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

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

なのでここではGoogle AnalyticsのMeasurement Protocolを利用します。

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

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

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

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

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

<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ロード後に処理が走っていない場合があるため、

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を利用する

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

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

念の為、Google Analyticsのjsが弾かれているか確認します。
ここで、弾かれていることを確認できれば、Measurement Protocolを利用することになりますが、そのままAjaxでGoogle宛にPOSTすると、ここでもHTTPリクエストを弾かれてしまいます。

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

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

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

です。
同一ユーザーを識別するcidsessionStorageに格納しておくと扱いやすいと思います。

またGoogle Analytics上で広告ブロックユーザーを別途で照会したい場合は、キャンペーンを利用すると良いです。
※使用用途を間違えていることは承知です笑

まとめ

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

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

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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away