비개발자를 위한 네이버 검색광고 API: 하루 성과 조회와 단위 확인
가상 데이터로 캠페인 이름과 통계를 연결한 뒤 네이버 검색광고 API로 캠페인 하나의 하루 성과를 조회합니다. 인증 서명, 계정 ID, 일별 응답과 비용 및 ROAS 단위를 차례로 확인합니다.

목차
세 줄로 먼저 읽기
이번 방문에서 한 편은 바로 볼 수 있습니다.
네이버 광고 보고서를 자동으로 만들고 싶다면 캠페인 하나의 하루 비용을 조회해 광고 관리자와 맞추는 것부터 시작합니다. 처음부터 모든 계정과 매체를 합치면 어떤 단계에서 숫자가 달라졌는지 찾기 어렵습니다.
API는 프로그램이 광고 시스템에 자료를 요청하는 기능입니다. 여기서는 광고 설정을 바꾸지 않고 조회한 정보를 CSV, 즉 표 형태의 텍스트 파일로 저장합니다. 계정 연결이 없다면 먼저 가상 자료 연습을 할 수 있습니다.
이름 정보와 성과 정보를 연결합니다
캠페인 목록에는 ID와 이름 같은 기본 정보가 있고, 통계에는 노출과 클릭, 비용이 있습니다. ID를 기준으로 두 정보를 연결하는 작업을 조인이라고 합니다. 이름은 바뀌거나 중복될 수 있어 연결 기준으로 적합하지 않습니다.
| 자료 | 조회 경로 | 먼저 확인할 필드 |
|---|---|---|
| 캠페인 기본 정보 | /ncc/campaigns 또는 특정 캠페인 경로 | nccCampaignId, name |
| 성과 통계 | /stats | impCnt, clkCnt, salesAmt |
name이 캠페인 이름입니다. 응답에 없는 campaignName을 읽으려 하면 이름을 가져오지 못합니다. 통계 응답도 최상위 자료 자체가 행 목록이라고 가정하지 않고 data 안의 행을 확인합니다. 공식 API 문서
계정 없이 연결과 계산을 연습합니다
Python 3가 있는 환경에서 아래 내용을 naver_practice.py로 저장하고 python3 naver_practice.py로 실행합니다. 외부 패키지와 실제 인증값은 필요하지 않습니다.
from decimal import Decimal
campaigns = [{"nccCampaignId": "DEMO_1", "name": "가상 캠페인"}]
stats = {"data": [{"id": "DEMO_1", "impCnt": 1000,
"clkCnt": 20, "salesAmt": 10000, "ror": 350}]}
names = {c["nccCampaignId"]: c["name"] for c in campaigns}
for row in stats["data"]:
cid = row["id"]
roas = None if row.get("ror") is None else Decimal(str(row["ror"])) / 100
print(cid, names.get(cid, "이름 미확인"), row.get("salesAmt"), roas)정상 출력은 DEMO_1 가상 캠페인 10000 3.5입니다. 이 자료는 여러 대상의 합계 응답 구조를 단순화한 가상 예시입니다. 아래 실제 조회 코드는 단일 캠페인의 일별 응답을 사용합니다.
ror 항목을 지우고 다시 실행하면 마지막 값이 None인지 확인합니다. None은 여기서 값이 확인되지 않았다는 뜻이며 0과 다릅니다. 실제 0이 반환된 경우와 응답에 필드가 없는 경우를 구분하는 연습입니다.
실제 조회 전에 준비할 것
네이버 공식 시작 안내는 광고 시스템의 ‘도구 > API 사용 관리’에서 서비스 신청과 키 발급을 진행하도록 설명합니다. 현재 계정의 메뉴와 사용 권한을 확인합니다. 다른 매체보다 무조건 간단하거나 모든 계정이 즉시 이용 가능하다고 가정하지 않습니다.
위 이미지는 기존 화면 예시입니다. 통합 광고주센터를 사용하는 경우 화면 이름과 이동 경로가 달라질 수 있습니다.
| 필요한 값 | 역할 |
|---|---|
검색광고 CUSTOMER_ID | 조회할 광고 계정을 구분 |
| API Key 또는 액세스 라이선스 | 요청자의 API 이용 정보 |
| Secret Key 또는 비밀키 | 요청의 인증 서명을 생성 |
| 캠페인 ID | 이번에 조회할 캠페인 하나를 지정 |
| 조회 날짜 | 광고 관리자와 대조할 하루를 지정 |
통합광고 ID인 adAccountNo와 검색광고 CUSTOMER_ID를 혼동하지 않습니다. 네이버는 통합 플랫폼 전환 후에도 검색광고 Open API에는 기존 CUSTOMER_ID를 사용해야 한다고 안내합니다. ID를 모르면 공식 ID 구분 안내와 접근 계정 목록 안내에서 확인합니다.
대행사가 조회할 때도 관리 계정 연결과 실제 접근 권한을 확인해야 합니다. 다른 광고주의 ID를 안다고 자료를 읽을 수 있는 것은 아닙니다. 키와 비밀키는 코드, 대화와 공개 저장소에 넣지 않습니다.
HMAC 서명은 무엇인가요?
HMAC-SHA256은 비밀키와 요청 정보를 이용해 인증용 서명을 만드는 방식입니다. 비밀키 자체를 전송하는 대신 요청 시각, HTTP 방식과 경로로 만든 서명을 보냅니다.
서명할 내용: 밀리초 시각.GET./stats
요청에 넣을 정보:
X-Timestamp, X-API-KEY, X-Customer, X-SignatureGET은 자료를 조회하는 HTTP 요청 방식입니다. 아래 코드는 캠페인을 만들거나 예산을 변경하는 요청을 보내지 않습니다.
실제 주소는 https://api.searchad.naver.com입니다. 서명에는 /stats 같은 경로를 사용하며, 전체 주소나 뒤의 검색 조건 문자열을 붙이지 않습니다. 요청을 다시 보낼 때는 새 시각으로 서명을 다시 만듭니다. 공식 서명 예제
단일 ID 조회와 여러 ID 조회를 구분합니다
| 목적 | 요청 조건 |
|---|---|
| 캠페인 하나의 일별 자료 | id 하나와 timeIncrement=1 |
| 여러 대상의 기간 합계 | ids와 timeIncrement=allDays |
공식 통계 문서에서 단일 ID와 여러 ID의 지원 조건이 다릅니다. 여러 ID 조회에 일별 옵션을 그대로 붙이지 않습니다. fields와 timeRange는 JSON 문자열로 만들어 보냅니다. JSON은 항목 이름과 값을 정해진 형식으로 적는 데이터 표현입니다. 통계 요청 명세
통계 날짜는 KST, 즉 한국 표준시 기준입니다. 처음에는 오늘처럼 집계가 진행 중인 날 대신 최근 완료된 하루를 고릅니다. 과거 날짜도 전환 등의 집계가 갱신될 수 있으므로 수집 시점을 남깁니다.
실제 조회 코드: 캠페인 하나와 날짜 하루
아래는 Python 3 표준 라이브러리만 사용하는 조회 예시입니다. naver_one_day.py로 저장합니다. 실행 환경에 다음 환경 변수가 준비돼 있어야 합니다. 환경 변수는 프로그램이 읽는 설정값입니다.
NAVER_SEARCHAD_API_KEY
NAVER_SEARCHAD_SECRET_KEY
NAVER_SEARCHAD_CUSTOMER_ID
NAVER_SEARCHAD_CAMPAIGN_ID
NAVER_SEARCHAD_DATE (YYYY-MM-DD 형식)회사에서 정한 비밀값 관리 방식으로 설정한 뒤 실행합니다. 터미널에서 키를 출력해 확인하거나 화면을 그대로 공유하지 않습니다. 계정 ID와 캠페인 ID, 날짜가 맞는지 먼저 대조합니다.
import os, json, time, hmac, hashlib, base64, csv
from datetime import date
from pathlib import Path
from urllib.parse import urlencode, quote
from urllib.request import Request, urlopen
from urllib.error import HTTPError
BASE = "https://api.searchad.naver.com"
API_KEY = os.environ["NAVER_SEARCHAD_API_KEY"]
SECRET = os.environ["NAVER_SEARCHAD_SECRET_KEY"]
CUSTOMER = os.environ["NAVER_SEARCHAD_CUSTOMER_ID"]
CID = os.environ["NAVER_SEARCHAD_CAMPAIGN_ID"]
DAY = date.fromisoformat(os.environ["NAVER_SEARCHAD_DATE"]).isoformat()
OUT = Path("naver_one_day.csv")
if OUT.exists():
raise FileExistsError("기존 CSV를 확인하고 다른 이름이나 폴더를 사용하세요.")
def get_json(path, params=None):
for attempt in range(3):
stamp = str(int(time.time() * 1000))
message = f"{stamp}.GET.{path}".encode()
signature = base64.b64encode(
hmac.new(SECRET.encode(), message, hashlib.sha256).digest()
).decode()
url = BASE + path + ("?" + urlencode(params) if params else "")
req = Request(url, headers={
"X-Timestamp": stamp, "X-API-KEY": API_KEY,
"X-Customer": CUSTOMER, "X-Signature": signature,
}, method="GET")
try:
with urlopen(req, timeout=30) as response:
return json.load(response)
except HTTPError as error:
if error.code == 429 and attempt < 2:
time.sleep(2 ** (attempt + 1))
continue
raise RuntimeError(f"조회 실패: HTTP {error.code}") from None
campaign = get_json("/ncc/campaigns/" + quote(CID, safe=""))
if not isinstance(campaign, dict) or campaign.get("nccCampaignId") != CID:
raise ValueError("요청한 캠페인과 응답 ID가 다릅니다.")
report = get_json("/stats", {
"id": CID,
"fields": json.dumps(["impCnt", "clkCnt", "salesAmt"]),
"timeRange": json.dumps({"since": DAY, "until": DAY}),
"timeIncrement": "1",
})
if not isinstance(report, dict):
raise ValueError("통계 응답이 예상한 객체 형식이 아닙니다.")
rows = report.get("data")
if not isinstance(rows, list):
raise ValueError("통계 응답의 data 목록을 확인하세요.")
output = []
for row in rows:
if not isinstance(row, dict):
raise ValueError("통계 행이 예상한 객체 형식이 아닙니다.")
if row.get("dateStart") != DAY or row.get("dateEnd") != DAY:
raise ValueError("응답 날짜가 요청한 하루와 다릅니다.")
output.append({
"date": DAY, "customer_id": CUSTOMER, "campaign_id": CID,
"campaign_name": campaign.get("name"),
"impressions": row.get("impCnt"), "clicks": row.get("clkCnt"),
"cost_vat_included": row.get("salesAmt"),
"collected_at_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
})
if output:
with OUT.open("x", newline="", encoding="utf-8-sig") as handle:
writer = csv.DictWriter(handle, fieldnames=list(output[0]))
writer.writeheader()
writer.writerows(output)
print(f"{len(output)}행 저장: {OUT}")
else:
print("조회 결과가 비어 있습니다. 날짜와 계정 상태를 확인하세요.")python3 naver_one_day.py로 실행한 뒤 CSV의 계정 ID, 캠페인 ID와 날짜를 봅니다. 비용과 클릭수를 광고 관리자에서 같은 조건으로 대조합니다. 비어 있는 필드는 CSV에서도 빈 칸으로 남고, 정상적으로 반환된 0은 0으로 저장됩니다. 출력의 date는 한국 시간 기준 조회 날짜이고, collected_at_utc는 세계 표준시인 UTC로 기록한 수집 시각입니다. 한국 시각은 UTC보다 9시간 빠릅니다.
코드는 429 응답일 때만 최대 세 번 시도합니다. 일정 간격을 두는 것만으로 호출 제한을 항상 피할 수 있는 것은 아닙니다. 계속 실패하면 반복 실행을 멈추고 공식 제한 안내를 확인합니다. 인증, 네트워크나 형식 오류를 무한히 재시도하지 않습니다.
비용과 ROAS를 합치기 전에 확인합니다
| 네이버 필드 | 뜻 | 보고서에서 확인할 점 |
|---|---|---|
impCnt | 노출수 | 노출 횟수와 도달 인원은 다름 |
clkCnt | 클릭수 | 방문 세션이나 구매수가 아님 |
salesAmt | 총비용 | 공식 통계 명세의 부가세 포함 기준 |
ctr | 클릭률 | 퍼센트 표시와 소수 비율 구분 |
ccnt, convAmt | 전환수와 전환매출액 | 구매만인지 다른 전환을 포함하는지 확인 |
ror | 광고수익률 | 퍼센트를 배수로 쓸 때 100으로 나눔 |
avgRnk | 평균 노출순위 | 지원하는 광고와 지면 범위 확인 |
salesAmt는 이름에 sales가 있지만 매출액이 아니라 비용입니다. 공식 통계 명세는 부가세 포함 비용으로 설명합니다. 다른 매체 비용과 비교할 때는 통화와 세금 포함 기준을 함께 맞춥니다. 구글 비용의 마이크로 단위를 100만으로 나누는 것은 금액 단위 변환일 뿐 원화 환산이 아닙니다. 네이버 통계 필드 명세
가상 예시로 같은 기준의 전환매출액이 35,000원, 비용이 10,000원이면 광고수익률은 350%, 배수로는 3.5배입니다. 비용이 0이거나 전환매출액을 확인하지 못했다면 정상 ROAS를 계산했다고 적지 않습니다. 여러 캠페인의 ROAS도 단순 평균하지 말고 같은 기준의 매출 합계를 비용 합계로 나눕니다. 네이버 성과 지표와 계산식
Meta의 링크 클릭도 네이버 클릭과 완전히 같은 행동 범위라고 볼 수 없습니다. 전환수 역시 각 매체의 추적 설정과 기여 기간에 따라 달라 같은 구매가 중복 집계될 수 있습니다. 단위만 바꾼 뒤 세 매체의 전환수를 실제 전체 주문수라고 합산하지 않습니다.
기간을 늘릴 때와 오류가 날 때
공식 통계 FAQ는 세분화 없는 일반 보고서의 제공 기간을 2년, 한 번의 조회 범위를 92일로 안내합니다. 세분화한 보고서나 다운로드 보고서는 종류별 조건이 다르므로 모든 데이터의 보관 기간을 365일로 단정하면 안 됩니다. 먼저 하루 조회를 검증하고 기간을 늘립니다. 통계 기간 FAQ
| 증상 | 먼저 확인할 것 |
|---|---|
환경 변수 이름이 나오는 KeyError | 해당 실행 환경에 필요한 설정이 있는지 |
URLError, 시간 초과 | 인터넷 연결과 서비스 상태를 확인한 뒤 다시 시도 |
| 인증 또는 권한 오류 | 라이선스와 비밀키, 검색광고 CUSTOMER_ID, 계정 권한 |
| 서명 오류 | PC 시각, 밀리초 값, GET과 서명한 경로 |
| 이름이 비어 있음 | 응답의 name과 캠페인 ID |
| 빈 결과 또는 누락 지표 | 날짜, 실제 집행, 요청 필드와 응답 구조 |
| 기존 CSV 오류 | 이전 결과를 확인하고 새 작업 폴더나 출력 이름 사용 |
첫 실행에서 KeyError가 나오면 코드에 키를 붙여 넣는 대신 오류에 나온 환경 변수 이름을 확인합니다. 회사의 설정 담당자에게 필요한 변수 이름만 전달하고, 키가 보이는 화면이나 전체 설정 목록은 공유하지 않습니다.
확인 질문입니다. 비용 필드가 없는 행과 비용이 0인 행을 모두 0원으로 저장해도 될까요? 그렇지 않습니다. 앞의 행은 비용을 확인하지 못한 상태이며, 뒤의 행만 응답에서 0이 확인된 것입니다.
첫날의 값이 맞으면 날짜만 바꿔 새 폴더에서 다시 실행하고 같은 기준으로 대조합니다. 여러 캠페인으로 확장할 때는 목록의 페이지 처리와 캠페인별 오류 기록도 추가합니다. 여기의 단일 캠페인 예시를 전체 계정 수집 코드라고 사용하지 않습니다.
AI에게 구현을 요청한다면
네이버 검색광고 API의 읽기 전용 보고서 코드를 검토해 주세요.
실제 키는 제공하지 않겠습니다.
공식 주소와 검색광고 CUSTOMER_ID를 사용하고,
단일 캠페인의 id와 timeIncrement=1로 날짜 하루를 조회합니다.
캠페인 이름은 name, 통계 행은 data에서 읽습니다.
누락값을 0으로 채우지 않고, 재시도 횟수와 요청 시간을 제한해 주세요.
CSV는 기존 파일을 덮어쓰지 않아야 합니다.
실제 API 호출 전에 가상 응답으로 정상, 빈 결과와 오류를 검사해 주세요.AI가 만든 코드에서도 주소와 필드, 날짜, 단위를 직접 확인합니다. 구글 Ads API와 Meta API를 합치는 단계는 각 매체의 하루 조회가 맞은 다음 진행합니다.
3줄 요약
-
네이버 검색광고 API는 검색광고 계정 ID와 발급받은 키를 사용하고 요청마다 인증 서명을 생성합니다.
-
처음에는 캠페인 하나의 하루 통계를 조회해 이름과 ID를 연결하고 응답의 날짜 및 비용을 광고 관리자와 맞춥니다.
-
ROAS의 퍼센트를 배수로 바꾸는 것과 비용의 부가세 및 전환 기준을 맞추는 것은 별도로 확인해야 합니다.

제대로 이해했는지 한 문제로 확인해 볼까요?
답을 고르면 바로 풀이가 나와요.
퍼센트 단위의 광고수익률 ror가 350일 때 보고서의 배수 단위로 바꾸면 얼마인가요?
이 글이 도움이 되었나요?
메일, 회의록, 리포트처럼 반복되는 일을 AI로 자동화하는 순서와 안전장치를 다룹니다
코스 전체 보기 →
새 글과 AI 소식을 메일로 받아 보세요
AI가 바꾸는 일과 도구, 측정 실무 이야기를 매주 한 번 보내 드려요.
