개발

웹사이트 광고차단 통계 분석 - Javascript와 GA4(구글 애널리틱스)를 이용한 데이터 수집

by 에루샤

개인 웹사이트, 상업용 사이트등을 이용하다보면 사용자 유입이나 마케팅에 대해서 따져봐야할게 한두개가 아니다.

긍정적인 부분에서의 유입과 사이트내 활동에서부터, 부정적인 활동 또한 조사를 해야한다.
긍정적인 부분은 추후 마케팅과 사이트내 컨텐츠 개선에 사용할 수 있고, 부정적인 부분은 서버단에서의 차단 또는  사용성 개선에 사용할 수 있기 때문이다.

이런 기능을 위해 쉽게 사용한것이 바로 '구글 애널리틱스'다.


손쉽게 자바스크립트를 사이트에 삽입하는것으로 사이트의 사용자에 대한 추적과 통계를 조사할 수 있으며, 단순히는 어떤 페이지, 어떤 상품의 관심도가 높은지부터 시작해서 어떤 페이지에서 사용자들이 제일 많이 이탈하는지, 어떤류의 사용자 환경에서의 접근이 높은지도 알 수 있는 아주 좋은 서비스다.


물론 이 블로그에도 적용이 되어있다.
여기서 더불어 나는 이 사이트의 서버비를 조금이나마 충당하려고 구글 애드센스를 이용해 짤짤이를 모으고 있는 상황이다. (라고해봤자 한달 호스팅비용도 안나오는게 현실이다... 운영할수록 마이너스지만 뭐 내 취미를 위한 비용이라 생각하면 월정액느낌으로 소비하는거니~)

웹사이트 광고차단 통계 분석 - Javascript와 GA4(구글 애널리틱스)를 이용한 데이터 수집채찍피티야, 애드블럭당하는 스핔이그려줘... 아... 아앗

근데 이런 애드센스를 이용한 광고는 생각보다 광고차단 프로그램으로 간단하게 막히는게 현실이다.
나는 필요이상의 과도한 광고를 넣지 않으려고 전면광고도 안쓰고 최소한의 광고만 밖으려하지만, 대부분의 커뮤니티는 그렇게해서는 서버비도 안나오기때문에 무자비하게 광고를 때려넣게되고, 자연스레 사용자들도 이런 무차별적인 광고가 싫기 때문에 광고 차단 프로그램을 쓰곤한다.

뭐 어쩔수 없는 부분이긴한데, 웃긴건 그런 커뮤니티 개발자들은 광고차단프로그램을 또 우회하는 광고를 박아버리거나 걍 깡으로 배너광고를 달기도한다.

각설하고 이 글을 쓰게된 이유는 어느날 문득 그런 생각이 들었다는 것이다.
이런 광고 차단을 파악할 수 있는 방법이 없을까? 라는것이다.


1. 구글 애널리틱스에서 관련지표가 있지않을까?

당연히 가장 처음에 찾아본것은 구글 애널리틱스다.

사용자의 대부분의 이벤트를 감지하고 기록하는 서비스니, 당연히 광고 차단에 대한 이벤트도 수집하지않을까? 였다.
더군다나 이번 경우는 자기들 회사 서비스인 구글 애드센스에 대한 이벤트니까말이다.
그래서 이리저리 뒤져보니 관련 이벤트가 하나 있긴했다.

구글 애널리틱스에서 구글 애드센스를 연동해두면 애드센스쪽에서 애널리틱스로 이벤트를 하나 쏴주는데, 그게바로 ad_impression 라는 이벤트다.

content_image광고 노출여부에 대한 지표

근데 해당 이벤트는 구글 애드센스의 adMob나 Google Ads 스크립트를 통해서 자동 수집된 데이터이며, 해당 이벤트는 '광고가 실제로 노출되었을때'를 기록하는 이벤트 값이라고 한다.

즉 애드센스 스크립트가 작동하고 광고단위입찰을 마쳐서 사용자에게 실제로 광고가 보인 이벤트수를 말하는 것이다.
그말은 즉슨 애드센스가 호출되기전 차단작업을 진행하는 애드블록, 애드가드계열이 작동했을때는 제대로된 수치를 얻을 수 없다는 것...


뭐, 쉽지 않을 줄은 알고있었다.
그럼 자연스럽게 알아가게된 과정은 어떻게 광고차단을 하는지?였다.


2. 광고차단 프로그램의 원리

그럼 먼저 차단 원리를 알아보자.

차단 방식은 총 2가지가 있는데,
첫 번째는 광고가 로드될 자리를 치우는 방법과
두번째는 광고요청을 하는 스크립트를 차단하는 방법이 있다.

웹사이트 광고차단 통계 분석 - Javascript와 GA4(구글 애널리틱스)를 이용한 데이터 수집애드블록의 광고 차단 원리는 무엇일까? - 요즘IT 발췌

DOM을 스캔해서 구글 애드센스가 대체할 요소(ads 클래스를 가진 요소)를 먼저 찾아내 없애는 선 처리방식과,
실제로 구글 애드센스의 스크립트 파일이 로드되는것을 막는 후처리방식이 있다.

전자의 경우엔 유명한 광고 서비스(애드센스)는 잘 막히나 영세한 업체까진 막기힘들고, 광고요청코드를 미묘하게 틀어버리면 쉽게 우회가 되므로 가벼운 차단 프로그램에서 잘 쓰이는 방식이다.
후자의 경우는 실제 광고사에서 쓰는 스크립트 파일 요청 자체를 중간에서 가로채 차단 하는 방식이므로 꽤 강력한  차단방식이라 볼 수 있다.


페이지 로드/랜더링 -> DOM 차단 -> 스크립트 차단 -> 페이지 뷰

이런 구조의 차단 방식이 있다고 보면된다.
그럼 이 차단을 감지하기위해서는 저 타이밍때 '같이 차단'될 수 있도록 더미를 만들어 놓고 그 더미가 변질되면 차단으로 판단할 수 있지 않을까?


3. 광고 차단 감지 스크립트

뭐 각자 사이트 환경이 달라서 그대로 따라할만한 코드는 아니지만 요즘은 AI가 알아서 잘 해주니 대략적인 흐름만 알아보도록 하자.

const adblockCheckState = {
    domBaitBlocked: null,
    scriptBlocked: null,
    hasSent: false
};

function getAdblockResult(domBaitBlocked, scriptBlocked) {
    if (domBaitBlocked && scriptBlocked) return 'both';
    if (domBaitBlocked) return 'dom_only';
    if (scriptBlocked) return 'script_only';
    return 'none';
}

function trySendAdblockCheckEvent() {
    if (adblockCheckState.hasSent) return;
    if (adblockCheckState.domBaitBlocked === null || adblockCheckState.scriptBlocked === null) return;

    adblockCheckState.hasSent = true;

    if (typeof window.gtag !== 'function') {
        return;
    }

    window.gtag('event', 'adblock_check', {
        adblock_dom_bait: adblockCheckState.domBaitBlocked ? 'yes' : 'no',
        adblock_script_blocked: adblockCheckState.scriptBlocked ? 'yes' : 'no',
        adblock_result: getAdblockResult(
            adblockCheckState.domBaitBlocked,
            adblockCheckState.scriptBlocked
        )
    });
}

function setAdblockDomBaitStatus(isBlocked) {
    if (adblockCheckState.domBaitBlocked !== null) return;
    adblockCheckState.domBaitBlocked = isBlocked;
    trySendAdblockCheckEvent();
}

function setAdblockScriptBlockedStatus(isBlocked) {
    if (adblockCheckState.scriptBlocked !== null) return;
    adblockCheckState.scriptBlocked = isBlocked;
    trySendAdblockCheckEvent();
}

function initAdblockCheck() {
    if (initAdblockCheck.hasInitialized) return;
    initAdblockCheck.hasInitialized = true;

    if (!document.body) {
        return;
    }

    const bait = document.createElement('div');
    bait.className = 'adsbox ad-banner google-ad adsbygoogle';
    bait.setAttribute('aria-hidden', 'true');
    bait.style.width = '1px';
    bait.style.height = '1px';
    bait.style.position = 'absolute';
    bait.style.left = '-9999px';
    bait.style.top = '-9999px';
    bait.style.pointerEvents = 'none';
    bait.style.opacity = '0';

    document.body.appendChild(bait);

    window.setTimeout(() => {
        const computedStyle = window.getComputedStyle(bait);
        const isBlocked = !bait.isConnected
            || bait.offsetHeight === 0
            || bait.offsetWidth === 0
            || computedStyle.display === 'none'
            || computedStyle.visibility === 'hidden';

        setAdblockDomBaitStatus(isBlocked);
        bait.remove();
    }, 300);
}

function initAd() {
    if (initAd.hasInitialized) return;

    const ads = document.querySelectorAll('ins.adsbygoogle');
    if (!ads.length) {
        setAdblockScriptBlockedStatus(false);
        return;
    }

    initAd.hasInitialized = true;

    const s = document.createElement('script');
    s.async = true;
    s.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-7022872918531044';
    s.crossOrigin = 'anonymous';

    s.onload = () => {
        setAdblockScriptBlockedStatus(false);

        ads.forEach((el) => {
            const hasStatus = el.hasAttribute('data-adsbygoogle-status');
            const hasAdStatus = el.hasAttribute('data-ad-status');
            const hasIframe = !!el.querySelector('iframe');
            const isFilled = el.getAttribute('data-ad-status') === 'filled';
            const isUnfilled = el.getAttribute('data-ad-status') === 'unfilled';

            if (!hasStatus && !hasAdStatus && !hasIframe && !isFilled && !isUnfilled) {
                window.adsbygoogle = window.adsbygoogle || [];
                window.adsbygoogle.push({});
            }
        });
    };

    s.onerror = () => {
        setAdblockScriptBlockedStatus(true);
    };

    document.head.appendChild(s);
}

document.addEventListener('DOMContentLoaded', function () {
    initAdblockCheck();
    initAd();
});
Copy

겁나 복잡해보이지만 결국 이런 소리다.

DOM차단, Script차단 두가지를 감지할 수 있게 스크립트를 구현하고 이를 GA4에 커스텀 이벤트로 쏴주는 코드라 보면 된다.

DOM차단을 감지하기위해 클래스를 유명한 광고 프로그램들이 디폴트로쓰는 클래스명(adsbox ad-banner google-ad adsbygoogle)으로 생성하고 미끼를 던져서 변질을 확인한다.

스크립트 차단은 더 간단한게, 구글 애드센스 스크립트를 비동기로 요청해 반응이 없는걸(onerror) 스크립트 차단으로 인식하면 끝.

이렇게 GA4로 넘겨두는 방식으로 구현해두면 앞으로 남은건 GA4에서 관련 지표를 만들고 보고서를 작성하는 일만 남은것이다.


4. GA4 광고차단 지표 등록

우선 이 이벤트는 우리가 직접 만든 이벤트이므로 이를 애널리틱스상에서 인지할 수 있도록 설정을 하나 해줘야한다.

애널리틱스 관리 -> 속성설정 -> 데이터 표시 -> 맞춤 정의로 가보자.

content_image애널리틱스 맞춤 정의 지표 추가

여기서 맞춤 측정기준 만들기를 누른다음 아래와 같이 지표를 추가해주자.

측정기준이름: adblock_result
범위: 이벤트
이벤트 매개변수: adblock_result
측정기준이름: adblock_dom_bait
범위: 이벤트
이벤트 매개변수: adblock_dom_bait
측정기준이름: adblock_script_blocked
범위: 이벤트
이벤트 매개변수: adblock_script_blocked
측정기준이름: adblock_enabled
범위: 이벤트
이벤트 매개변수: adblock_enabled


그리고 적용한 지표와 스크립트가 잘 작동하는지 어떻게 확인할 수 있을까?
바로 실시간 페이지에서 확인이 가능하다.

구글 애널리틱스의 보고서 -> 실시간 개요로 가보면 이벤트를 확인할 수 있는 부분에 아래처럼 adblock_check 이벤트가 뜨면 된다.

content_imageadblock_check 지표가 보여야한다

여기까지 왔다면 사실 기술적인 작업은 다 끝났다고 볼 수있다.

애널리틱스 지표는 대부분 보고서에 사용되려면 하루정도는 기다려야하니 하루뒤에 보고서를 만들어 보자.


5. GA4 보고서 작성

하루가 지났는가?
드디어 지난 1일간의 광고 차단 지표를 볼때가 왔다.

애널리틱스 > 탐색 > 새 탐색 분석 만들기를 클릭해 새 보고서를 만들어보자.
일단 좌측 사이드바에서 변수 -> 측정기준 -> +를 눌러보자.

content_image

그러면 화면 우측에서 사이드 패널이 나오는데, 여기서 사용자 설정 탭으로 가보자.
그러면 위와같이 아까 맞춤 정의에서 추가한 지표가 보일것이다.

여기서 adblock_result을 체크, 그리고 상단에 검색에서 '이벤트'라고 검색해 '이벤트 이름도 추가해주자.

content_image수치를 보기위한 이벤트 수 추가

그리고 다음으로는 측정항목 -> + 를 눌러 '이벤트 수'를 추가해주자.

그리고 차례대로 설정 사이드바에서

행: adblock_result
열: 비워둠
값: 이벤트 수
필터: 이벤트 이름, 다음과 정확하게 일치, adblock_check

를 지정해주자.
그러면...

content_image와우~ 스크립트 작동결과가 한눈에...

따란! 이렇게 광고 차단 여부를 애널리틱스 탐색 보고서에서 확인할 수 있다.
none은 광고차단이 안된 경우, dom_only는 DOM차단의 경우, script_only는 스크립트차단의경우, both는 DOM+스크립트 둘다 차단된 경우라 보면된다.

이걸 만약에 단순화해서 쉽게 차단/차단안됨으로만 구분해서 보고싶다면 아래와 같이 세그먼트 항목을 추가해보도록하자.


세그먼트 -> + -> 새 세그먼트 만들기 -> 이벤트 세그먼트로 가면 세그먼트를 맘대로 구성할 수 있다.

content_image광고 허용 세그먼트 추가
content_image광고 차단 세그먼트 추가

위 그림과 같이 조건을 걸어서 세그먼트 항목을 묶어서 생성해주자.
그리고 이걸 아까 설정의 세그먼트에 적용하고, 기존의 행을 날리면...

content_image세로열에 깔끔하게 허용과 차단으로 구분된다.

이렇게 상단 세그먼트에서 광고 허용과 차단으로 깔끔하게 분리해서 볼 수 있다.
여기서 측정기준을 추가한 후 열 데이터를 기기 카테고리등을 입력하면 해당 이벤트가 발생된상황에서의 부가자료를 아래처럼 볼수도 있다.

content_image확실히 모바일 애드블럭류가 스크립트 차단은 엄청 잘한다.

만약 저런 라인테이블방식이 아니라 도넛차트처럼 비중으로 보고싶다면,
세그먼트를 제거하고 분류값을 adblock_result로 두면 아래와 같이 데이터를 확인할 수 있다.

content_image광고 허용과 차단을 도넛차트로 볼 수 있다.



이렇게 광고차단 프로그램에 대한 통계를 얻어보는 방식을 알아보았다.
뭐 이런거야 요즘 AI가 하라는대로 하면 뚝뚝딱딱 다 알아서 알려주지만, 그래도 나름의 작업기록은 필요하니까 이렇게 글로 정리해보았다.

끝!

content_image사실 그냥 광고 봐주시는 분들이 더 많아서 스피키는 쪼아요!

#JavaScript #SEO #광고
5 개의 댓글
고양만두 2시간 전
요즘 광고 차단을 많이 쓰는 건 알았는데.. 통계 데이터로 분석할 수 있도록 수집하는 방법이 있는줄 몰랐네요 ㅎㅎ; 스크립트 코드를 삽입할 수 있는 티스토리, 구글 블로그, 워드프레스로 블로그 운영자들한테는 개꿀팁인것 같아요.
에루샤 프로필 이미지 2시간 전에 좋아요를 받았습니다
답글
고양만두 2시간 전
해외에는 이런 꿀팁 정보를 본다면 팁을 주는 차원에서 광고를 클릭해준다는데.. 한국은 굳이 광고 차단 프로그램까지 쓰는거보면..좀 아쉽네요 ㅎㅎ;
에루샤 프로필 이미지 2시간 전에 좋아요를 받았습니다
답글
에루샤 certified 2시간 전
@고양만두
사실 그냥 봐주는것만해도 고맙고, 애드센스애들도 머리가좋아서 그런 무효클릭은 또 무섭게 잡아내서 배제시키더라구요ㅋㅋㅋ
답글
키니하라 1시간 전
가끔 막시민이 나와서 클릭을 유도하는데, 무섭다 데스와
에루샤 프로필 이미지 2분 전에 좋아요를 받았습니다
답글
에루샤 certified 1분 전
@키니하라
광고: (전) 막시민 서버 랭커 in 100님께만 특별히 보여드리는 겁니다. (찡긋)
답글
×