컨텍스추얼 타겟팅
페이지 본문의 컨텍스트(카테고리·키워드·지역·데모그래픽)를 브라우저 안에서 분석하거나, 매체가 보유한 검색어 등의 키워드를 직접 주입하여 광고 요청에 컨텍스추얼 타겟팅(Contextual Targeting) 정보를 함께 전달할 수 있습니다.
- context: 컨텍스트 분석 스크립트(
ctx-sdk.js)가 본문을 분석한 결과를 광고 슬롯에 전달합니다. - keyword: 매체가 검색어 등 단순 키워드를 광고 슬롯에 직접 전달합니다.
두 값은 서로 독립적이며, 함께 설정하면 두 값 모두 광고 요청에 포함됩니다.
1. Concepts
| 항목 | context | keyword |
|---|---|---|
| 설정 위치 | defineAdSlot()의 adSlotInfo.context | defineAdSlot()의 adSlotInfo.keyword |
| 값 | ctx-sdk.js의 분석 결과 객체(res.context) | 문자열(검색어 등 단순 키워드) |
| 생성 주체 | ctx-sdk.js의 온디바이스(On-device) 분석 | 매체 |
| 용도 | 본문 기반 컨텍스추얼 타겟팅 | 분석 결과가 없을 때의 폴백 또는 분석 결과 보완 |
- 분석은 사용자 브라우저 안에서만 수행됩니다. 본문 텍스트는 어떤 서버로도 전송되지 않으며, 광고 요청에는 분석 결과만 포함됩니다.
- 쿠키나 사용자 식별자를 사용하지 않으므로 쿠키리스(Cookieless) 환경에서도 동작합니다.
- 매체는 분석 결과 객체를 가공하지 않고 그대로
context에 전달하면 됩니다. 광고 요청 파라미터 인코딩은 SDK가 처리합니다.
전체 흐름은 다음과 같습니다.
2. 준비 사항
| 항목 | 설명 |
|---|---|
| 광고 유닛 ID | 슬롯별로 발급받은 adUnitId |
| 광고 슬롯 엘리먼트 | 슬롯마다 고유한 id를 가진 div 엘리먼트 |
| 분석 스크립트 | https://sdk.naverads.net/libs/ctx-check/ctx-sdk.js |
| 키워드 번들 | ctx_model2_100k.bin. 분석 스크립트 경로 아래의 버전 디렉터리(<버전>/ctx_model2_100k.bin)에서 자동으로 로드됩니다. 분석 스크립트 버전과 항상 짝이 맞는 번들을 받으며, SDK가 갱신되어도 분석 스크립트 URL은 바뀌지 않습니다. |
| 본문 영역 | 본문을 감싸는 엘리먼트의 CSS 선택자(예: #postViewArea, article). 지정하지 않으면 자동으로 추출합니다. |
| 지원 브라우저 | Chrome 85+, Safari 14+, Firefox 79+, Edge 85+ (IE 미지원) |
3. 컨텍스트 분석 결과 적용하기
3-1. 스크립트 삽입
-
<head>또는 본문 앞에 아래 스크립트를 삽입합니다.gfp-core.js는async로,ctx-sdk.js는defer로 로드합니다.<script async src="https://sdk.naverads.net/libs/glad/prod/gfp-core.js"></script><script>window.gladsdk = window.gladsdk || { cmd: [] };</script><script defer src="https://sdk.naverads.net/libs/ctx-check/ctx-sdk.js" data-root="#postViewArea"></script>defer스크립트는 HTML 파싱을 막지 않으면서 DOMContentLoaded 이전에 실행이 보장됩니다. 따라서 DOMContentLoaded 이후에는 별도의 큐 없이gladsdk.CtxCheck를 바로 호출할 수 있습니다.data-root에 본문 영역 선택자를 지정합니다. 자세한 내용은 5. 본문 영역 지정하기를 참고해주세요.ctx-sdk.js는gladsdk.CtxCheck(고정 별칭gladsdkCtxCheck) 외에 전역 변수를 만들지 않으며, 로드만으로는 분석을 수행하지 않습니다.
3-2. 상단 광고 슬롯 - 분석을 기다리지 않고 즉시 요청
-
첫 화면에 노출되는 광고 슬롯은 분석을 기다리지 않고 기존과 동일하게 요청합니다.
<div id="ad-top"></div><script>window.gladsdk.cmd.push(function () {window.gladsdk.defineAdSlot({ adUnitId: 'TOP_UNIT_ID', adSlotElementId: 'ad-top' });window.gladsdk.displayAd('ad-top');});</script>
3-3. 하단 광고 슬롯 - 분석 완료 후 context를 포함하여 요청
-
본문 아래의 광고 슬롯은 분석이 끝난 뒤 정의합니다. 분석은 DOMContentLoaded에서 시작하고, 광고 슬롯 정의와 요청만
gladsdk.cmd에 추가합니다.<article id="postViewArea">…본문…</article><div id="ad-mid"></div><div id="ad-bottom"></div><script>document.addEventListener('DOMContentLoaded', function () {// 1) 분석 시작 - recommend()는 로드 시간을 포함해 3초(maxMs 기본값) 안에 항상 응답합니다.// 스크립트가 로드되지 않은 경우(차단, 404 등)에만 null로 처리하여 context 없이 광고를 요청합니다.var analysis = window.gladsdk.CtxCheck? window.gladsdk.CtxCheck.recommend({ idle: true }).catch(function () { return null; }): Promise.resolve(null);// 2) 분석이 끝나면 광고 슬롯을 정의하고 광고를 요청합니다.analysis.then(function (res) {window.gladsdk.cmd.push(function () {['ad-mid', 'ad-bottom'].forEach(function (id) {var adSlotInfo = { adUnitId: 'BOTTOM_UNIT_ID', adSlotElementId: id };if (res && res.context) {adSlotInfo.context = res.context;}window.gladsdk.defineAdSlot(adSlotInfo);window.gladsdk.displayAd(id);});});});});</script> -
아래 세 가지 규칙을 지켜주세요.
res.context는 가공하지 않고 그대로 전달합니다. 객체 규격은ctx-sdk.js가 만들고, 광고 요청 인코딩은 SDK가 처리합니다.res.context가null이면context를 설정하지 않습니다. 분석 결과가 없거나 로드가 시간 상한을 넘긴 경우이며, 광고는 context 없이 정상 요청됩니다.- 상단 광고 슬롯을 지연시키지 않습니다.
recommend()는 로드 시간을 포함해 3초 안에 항상 응답하며, 시간 상한을 넘기면 그때까지 분석한 부분 결과(partial: true)를 반환합니다. 매체 코드에 별도의 타이머를 둘 필요가 없습니다(defer로 로드한 경우.async로 로드하는 경우는 7. 로딩 방식별 주의 사항을 참고해주세요).
-
설정된 값은 adSlot.getContext()로 확인할 수 있습니다.
-
전체 코드는 컨텍스추얼 타겟팅 예제 - 기본 예제를 참고해주세요.
3-4. 중복 제어와 함께 사용하기
-
같은 페이지의 여러 광고 슬롯에 동일한 광고가 노출되지 않도록 하려면 광고 중복 제어 매니저로 광고를 요청합니다. context 전달 방식은 동일하며, 3-2와 3-3의 광고 요청 부분만 아래와 같이 변경합니다.
var adDedupManager; // 상단, 하단 광고 슬롯이 공유하는 중복 제어 매니저// 상단: 즉시 요청window.gladsdk.cmd.push(function () {adDedupManager = window.gladsdk.setAdDeduplication('dedupReqId', 2); // 광고 슬롯 수 - 1var topAdSlot = window.gladsdk.defineAdSlot({ adUnitId: 'TOP_UNIT_ID', adSlotElementId: 'ad-top' });adDedupManager.displayAds([topAdSlot]);});// 하단: 분석 완료 후 요청document.addEventListener('DOMContentLoaded', function () {var analysis = window.gladsdk.CtxCheck? window.gladsdk.CtxCheck.recommend({ idle: true }).catch(function () { return null; }): Promise.resolve(null);analysis.then(function (res) {window.gladsdk.cmd.push(function () {var adSlots = ['ad-mid', 'ad-bottom'].map(function (id) {var adSlotInfo = { adUnitId: 'BOTTOM_UNIT_ID', adSlotElementId: id };if (res && res.context) {adSlotInfo.context = res.context;}return window.gladsdk.defineAdSlot(adSlotInfo);});adDedupManager.displayAds(adSlots); // 상단 광고를 제외 기준으로 ad-mid, ad-bottom 순서대로 요청});});}); -
아래 규칙을 지켜주세요.
- 중복 제어 매니저의
displayAds()로만 광고를 요청합니다.gladsdk.displayAd()로 요청한 광고 슬롯은 중복 제어에서 제외됩니다. - 광고는 배열 순서대로 순차 요청됩니다. 앞 광고 슬롯의 응답을 받은 뒤 다음 광고 슬롯을 요청합니다. 같은 중복 제어 매니저에
displayAds()를 여러 번 호출해도 요청은 이어서 진행되고 중복 제외 기준도 누적되므로, 상단은 즉시 요청하고 하단은 분석 완료 후 요청하는 흐름을 그대로 유지할 수 있습니다. maxDedupReqCount는 광고 슬롯 수 - 1로 명시합니다. 광고 슬롯이 3개이면2입니다.context는 광고 슬롯 단위로 설정합니다. 중복 제어 적용 여부와 관계없이 광고 슬롯별 광고 요청에 포함됩니다.
- 중복 제어 매니저의
-
광고 요청의 QueryString에서 적용 여부를 확인할 수 있습니다. 첫 번째 광고 요청에는
ba,bx값이 없고 두 번째 요청부터 포함되며, 하단 광고 요청에는 context 값이 함께 포함됩니다. 자세한 내용은 중복 제어 적용 여부 확인을 참고해주세요. -
전체 코드는 컨텍스추얼 타겟팅 예제 - 중복 제어 적용 예제를 참고해주세요.
3-5. 첫 화면 광고 슬롯에 context를 적용하는 경우
- 기본 권장은 첫 화면 광고 슬롯은 즉시 요청하고(3-2), 본문 아래 광고 슬롯에만 context를 전달하는 방식(3-3)입니다.
- 첫 화면 광고 슬롯에도 context가 꼭 필요한 경우의 구성은 컨텍스추얼 타겟팅 예제 - 첫 화면 광고 슬롯에 context 적용하기를 참고해주세요. 이 경우 첫 화면 광고는 분석을 기다리는 만큼 늦게 요청됩니다.
4. 매체 주입 키워드 설정하기
-
매체가 이미 보유한 검색어 등의 단순 키워드를
adSlotInfo.keyword에 설정하면 광고 요청에 함께 전달됩니다. -
키워드는 문장이 아닌 단어 형태로 전달합니다.
아이팟 프로,아이팟 신형처럼 여러 단어로 된 키워드도 가능하며, 콘텐츠 제목은 전달하지 않습니다. -
컨텍스트 분석 결과를 사용할 수 없는 경우(스크립트 미로드,
timeout: 'load',status: 'empty'등)의 폴백으로 사용하거나, 분석 결과를 보완하는 용도로 사용합니다. -
context와 독립적으로 동작하며, 둘 다 설정하면 두 값 모두 전달됩니다.window.gladsdk.cmd.push(function () {window.gladsdk.defineAdSlot({adUnitId: 'adUnitId',adSlotElementId: 'division',keyword: '아이팟 프로',});window.gladsdk.displayAd('division');}); -
광고 슬롯 단위로 설정하므로 피드형 지면처럼 슬롯마다 다른 콘텐츠가 노출되는 경우 슬롯별로 다른 키워드를 지정할 수 있습니다.
window.gladsdk.cmd.push(function () {feedItems.forEach(function (item, index) {var id = 'feed-ad-' + index;window.gladsdk.defineAdSlot({ adUnitId: 'adUnitId', adSlotElementId: id, keyword: item.keyword });window.gladsdk.displayAd(id);});}); -
값 처리 규칙
- 연속된 공백(개행, 탭 포함)은 단일 공백으로 합쳐지고 앞뒤 공백은 제거됩니다.
- 설정하지 않았거나 빈 문자열, 공백만 있는 문자열인 경우 전달되지 않습니다.
- 문자열이 아닌 값(숫자, 객체, 배열,
null등)은 오류 없이 무시됩니다. - 길이 제한은 없으며, 인코딩은 SDK가 처리합니다.
-
설정된 값은 adSlot.getKeyword()로 확인할 수 있습니다.
5. 본문 영역 지정하기
분석 품질은 본문 영역을 정확히 지정하는 데서 가장 크게 좌우됩니다. 광고, 댓글, 추천 목록이 섞이지 않은 본문 컨테이너를 지정해주세요.
| 방법 | 예시 | 비고 |
|---|---|---|
| 스크립트 속성 | <script defer src="…/ctx-sdk.js" data-root="#postViewArea"> | 가장 간단한 방법. 이후 recommend() 호출에 기본으로 적용됩니다. |
| 호출 옵션(선택자) | recommend({ root: '.article-body' }) | 페이지 유형별로 다르게 지정할 때 |
| 호출 옵션(Element) | recommend({ root: document.querySelector('article') }) | 이미 엘리먼트 참조가 있을 때 |
| 호출 옵션(함수) | recommend({ root: () => document.querySelector('#a') || document.querySelector('#b') }) | 템플릿이 여러 가지일 때. 함수는 호출 시점에 평가됩니다. |
| 미지정 | recommend() | 의미 태그와 텍스트 밀도를 기반으로 자동 추출합니다. 선택자가 매칭되지 않는 경우에도 자동 추출로 동작합니다. |
- 본문이 iframe 안에 렌더링되는 지면은 해당 iframe 문서 안에 스크립트를 삽입해야 합니다. 상위 문서에서 실행하면 본문을 찾을 수 없어
status: 'empty'가 반환됩니다. 스크립트는 iframe 내부를 탐색하지 않습니다. - 제목은
<title>,og:title,h1에서 자동으로 읽으며 사이트명 꼬리는 제거됩니다. 본문이 짧을수록 제목의 비중이 커집니다. - 댓글 영역이 본문 컨테이너 안에 있으면 함께 분석되지만 가중치가 절반으로 낮아집니다. 가능하면 댓글 영역은
root밖에 두세요.
6. recommend() 옵션과 응답
6-1. 옵션
| 옵션 | 기본값 | 설명 |
|---|---|---|
idle | true | 계산 중간마다 메인 스레드에 양보하여 광고 렌더링과 페이지 상호작용을 막지 않습니다. 운영 환경에서는 true를 권장합니다. false인 경우 한 번에 계산합니다(동기 250~500ms 수준). |
root | data-root 값 | 본문 영역. 5. 본문 영역 지정하기 참고 |
count | 2 | 최종 키워드 개수(kw 길이) |
minSim | 0.8 | 키워드 매칭 최소 유사도. 낮출수록 키워드가 늘어나지만 정확도가 떨어집니다. 기본값 사용을 권장합니다. |
maxMs | 3000 | 응답 시간 상한(ms). 번들 로드 시간을 포함합니다. 상한을 넘기면 그때까지의 부분 결과(partial: true)로 응답하고, 로드가 끝나지 않았다면 빈 응답(timeout: 'load', context: null)을 반환합니다. 0이면 제한이 없습니다. |
text | - | DOM 대신 전달한 문자열을 분석합니다. |
6-2. 응답
{
status: 'ok' | 'empty' | 'no_fill',
context: {
top_cat: '자동차>신차·중고차·시세',
iab: ['30'],
kw: ['GV90', '제네시스'],
region: '용인|역북동||',
demo: 'F|30S',
safety: ['legal'],
} | null,
partial: false, // true면 maxMs 상한으로 중단된 부분 결과 (context는 그대로 사용 가능)
timeout: 'load', // (있을 때만) 번들 로드가 상한 안에 끝나지 않음 - context는 null
elapsedMs: 232, // 호출부터 응답까지 걸린 시간 (로드 포함)
// topCategory, keywords, region, demo, safety, terms 등 - 디버깅용 상세 필드. 광고 요청에는 사용하지 않습니다.
}
| status | 의미 | 매체 처리 |
|---|---|---|
ok | 분석 성공. context에 값이 있습니다. | context: res.context로 광고 슬롯 정의 |
empty | 본문에서 광고용 키워드를 찾지 못했습니다(본문 미노출, 너무 짧은 본문, 비상업 주제 등). context는 대부분 null입니다. | context를 설정하지 않고 광고 요청 |
no_fill | 여론조사 등 비상업 콘텐츠로 판정되었습니다. context는 null입니다. | 위와 동일 |
empty + timeout: 'load' | 번들 로드가 maxMs 안에 끝나지 않았습니다(느린 네트워크). context는 null입니다. | 위와 동일. 로드는 계속 진행되므로 다음 호출은 정상 동작합니다. |
context의 각 키는 값이 있을 때만 포함됩니다.iab는 IAB Content Taxonomy의 Unique ID 문자열 배열입니다. 영문자가 포함된 ID도 있으므로 숫자로 변환하지 않습니다.safety가 있으면 해당 이슈(정치, 형사법률 등)와 관련된 키워드는 이미kw에서 제외된 상태입니다. 매체가 별도로 걸러낼 필요가 없습니다.
6-3. 이벤트 (선택)
document에서 아래 이벤트가 발생합니다. 로깅이나 모니터링 용도로 사용할 수 있습니다.ctxcheck:ready: 번들 로드 완료ctxcheck:result: 분석 완료 (event.detail에 응답 객체 전달)
7. 로딩 방식별 주의 사항
| 상황 | 동작 | 처리 방법 |
|---|---|---|
defer 로드 + DOMContentLoaded 안에서 호출 (권장) | 항상 로드가 완료된 상태 | 큐 없이 gladsdk.CtxCheck.recommend() 직접 호출 |
defer 로드 + 인라인 스크립트에서 바로 호출 | 인라인 스크립트가 defer 스크립트보다 먼저 실행되어 gladsdk.CtxCheck가 undefined | DOMContentLoaded 핸들러 안으로 호출을 옮기거나, ctx-sdk.js 뒤에 둔 별도 defer 외부 스크립트에서 호출 |
태그 매니저 등으로 async 삽입이 강제되는 경우 | 실행 시점이 보장되지 않음 | 아래 선행 큐와 바깥 타이머 사용 |
gfp-core.js가 window.gladsdk를 교체하는 경우 | gfp-core.js는 로드 시 window.gladsdk를 새 객체로 교체 | ctx-sdk.js가 자동으로 다시 연결하므로 별도 처리 불필요. 선행 설정은 gladsdk.CtxCheck가 아닌 gladsdkCtxCheck에 지정 |
-
async삽입이 불가피한 경우 선행 큐를 사용합니다. 로드 전에는 큐에 쌓이고, 로드 후에는 즉시 실행됩니다. 콜백 인자가{ error }형태이면 실패입니다. -
선행 큐를 사용할 때는 매체 코드에 바깥 타이머를 함께 둡니다.
maxMs는ctx-sdk.js가 큐의recommend()를 실행한 시점부터 측정됩니다. 스크립트가 늦게 로드되면 그만큼 늦게 시작합니다.- 스크립트가 실행되지 않으면(차단, 404, 미지원 브라우저 등) 콜백이 호출되지 않습니다. 광고 요청을 보장하려면 바깥 타이머가 필요합니다.
maxMs는 바깥 타이머보다 짧게 지정합니다(예: 바깥 타이머 3000ms,maxMs: 2000). 두 값의 차이는 스크립트 로드 지연에 대한 여유분입니다.
window.gladsdkCtxCheck = window.gladsdkCtxCheck || { q: [] };var done = false;function finish(res) {if (done) return; // 분석 결과와 바깥 타이머 중 먼저 온 쪽으로 한 번만 요청done = true;clearTimeout(timer);requestBottomAds(res && !res.error ? res : null); // res.context가 있으면 광고 슬롯에 전달}var timer = setTimeout(function () { finish(null); }, 3000); // 스크립트 미로드 대비 - maxMs보다 길게window.gladsdkCtxCheck.q.push(['recommend', { idle: true, maxMs: 2000 }, finish]); -
설정을 코드로 지정하려면 스크립트 로드 전에 아래와 같이 선언합니다.
window.gladsdkCtxCheck = { q: [], config: { root: '#postViewArea', count: 2 } };
8. 성능 및 리소스
| 항목 | 값 | 비고 |
|---|---|---|
ctx-sdk.js | 62KB (br 22KB) | 외부 의존성 없음 |
키워드 번들 ctx_model2_100k.bin | 991KB (br 약 655KB) | 최초 1회 다운로드. 버전 디렉터리 경로라 내용이 바뀌지 않으며, 이후 브라우저 캐시 사용 |
| 번들 로드 및 초기화 | 약 120ms | 최초 recommend() 호출 시 1회 |
| 분석 | 동기 250~500ms / idle: true는 페이지 상황에 따라 더 소요 | maxMs(기본 3초, 로드 포함) 안에 항상 응답 |
| 메인 스레드 | idle: true면 계산 중간마다 양보 | 상단 광고 렌더링과 스크롤을 막지 않음 |
| 네트워크 | 스크립트와 번들 다운로드만 발생 | 분석 중 외부 요청 없음. 본문 텍스트는 기기 밖으로 전송되지 않음 |
- CSP(Content Security Policy)를 운영하는 지면만 아래 허용 목록을 적용합니다. CSP를 사용하지 않는 지면은 별도 작업이 필요하지 않습니다.
script-src https://sdk.naverads.net
connect-src https://sdk.naverads.net
script-src는gfp-core.js와ctx-sdk.js로드에,connect-src는 키워드 번들(ctx_model2_100k.bin) 다운로드에 필요합니다. 키워드 번들은 분석 스크립트와 같은 호스트에서 받습니다.- SDK는
eval,new Function을 사용하지 않으므로unsafe-eval은 필요하지 않습니다.
9. 개인정보 및 브랜드 세이프티(Brand Safety)
- 분석은 모두 브라우저 안에서 완료됩니다. 광고 요청에는
context의 요약값(카테고리, 키워드 몇 개, 지역, 데모그래픽 코드)만 포함되며, 본문 원문이나 사용자 식별 정보는 생성하지 않습니다. demo는 본문이 대상으로 하는 독자층(예:F|30S)이며, 방문자를 추정한 값이 아닙니다.- 정치, 형사법률, 사고 등 브랜드 세이프티(Brand Safety) 이슈가 검출되면 관련 키워드를 제외하고
safety에 이슈 코드를 포함합니다. 페이지 자체의 광고 노출을 막지는 않으며, 여론조사와 같은 비상업 콘텐츠만no_fill로 판정됩니다.
10. 적용 확인
-
적용한 페이지의 브라우저 개발자 도구 콘솔에서 분석 결과를 확인할 수 있습니다.
const res = await gladsdk.CtxCheck.recommend({ idle: false });console.log(res.status, res.context); -
광고 슬롯에 설정된 값은 adSlot getter 함수로 확인할 수 있습니다.
const adSlot = gladsdk.findAdSlot('ad-mid');console.log(adSlot.getContext(), adSlot.getKeyword());
11. 문제 해결
| 증상 | 원인 | 조치 |
|---|---|---|
gladsdk.CtxCheck is undefined | 인라인 스크립트가 defer 스크립트보다 먼저 실행됨, async로 삽입됨, 또는 스크립트 로드 실패(차단, 404 등) | 호출을 DOMContentLoaded 안으로 옮기고, async가 불가피하면 gladsdkCtxCheck.q 선행 큐를 사용합니다(7. 로딩 방식별 주의 사항). 호출 전에 window.gladsdk.CtxCheck 존재 여부를 확인하면 미로드 시에도 광고는 정상 요청됩니다. |
항상 status: 'empty' | 본문이 iframe 안에 있는데 상위 문서에서 실행 / data-root 선택자가 본문과 다름 / 본문이 매우 짧음 | iframe 문서 안에 스크립트를 삽입합니다. 콘솔에서 document.querySelector(선택자)로 본문이 선택되는지 확인합니다. |
| 키워드가 광고나 추천 목록의 어휘로 추출됨 | root가 본문보다 넓게 지정됨 | 본문 컨테이너로 범위를 좁힙니다. 자동 추출은 광고와 내비게이션 영역을 제거하지만, 지정한 영역 안은 그대로 분석합니다. |
하단 광고가 매번 3초 뒤에 요청됨 (partial: true) | 본문이 매우 길거나 페이지가 바빠 분석이 지연됨 | 정상 동작이며 3초 시점의 부분 결과가 전달됩니다. 더 빨리 요청하려면 recommend({ maxMs: 1500 })처럼 상한을 낮추거나, 스크롤 진입 시점에 광고 슬롯을 정의합니다. |
context가 null | empty / no_fill, 또는 브랜드 세이프티 이슈로 키워드가 모두 제외됨 | 정상 동작이며 context 없이 광고가 요청됩니다. 필요한 경우 keyword를 함께 설정합니다. |