🟦 소개
블로그를 운영하다 보면
“오늘은 무슨 키워드로 글을 써야 사람들은 많이 보고, 나는 상위노출이 될까…?”
이 고민이 하루도 빠지지 않고 따라옵니다.
검색량은 높은데 경쟁이 너무 치열한 키워드를 잡으면
글을 10개, 20개 써도 노출이 묻혀버리죠.
그래서 저는 결심했습니다.
“네이버는 이미 검색량과 문서수(경쟁량) 데이터를 모두 가지고 있다.
그럼 이걸 자동으로 뽑아서 ‘좋은 키워드’만 골라주면 최고 아닌가?”
그래서 만들어진 것이 바로…
🔥 ‘블로그 자동 키워드 분석 시스템’
네이버 검색광고 API → 검색량(시장 수요)
네이버 검색 API → 문서수(경쟁 정도)
파이썬으로 자동 계산 → 경쟁률이 낮은 키워드 자동 선별
이 시스템이면 이제:
✔ 어떤 키워드를 먼저 써야 하는지
✔ 어떤 키워드는 피해야 하는지
✔ 어디에 ‘숨은 황금 키워드’가 있는지
모두 자동으로 눈앞에 펼쳐집니다.
🟦 진행 방법
🌳 전체 구조를 비유로 설명해볼게요
이 시스템을 만들기 위해서는
네이버 본사 건물 두 곳을 방문해야 합니다.
1번 건물: 네이버 검색광고 API — ‘시장 정보 센터’
→ 사람들이 매달 얼마나 검색하는지 데이터가 있음2번 건물: 네이버 검색 API — ‘경쟁자 데이터 센터’
→ 해당 키워드로 이미 작성된 블로그 글의 개수가 있음
우리는 이 두 건물에 들어가기 위해
출입카드 3종 세트(API 키) 를 발급받아야 해요.
🧭 1단계 — 네이버 검색광고 API 키 발급 (검색량 담당)
📍 사이트
✔ 해야 할 일
광고 계정 생성
검색 광고 클릭 후 상단 메뉴 → API 관리
Access License Key 복사
Secret Key 복사
Customer ID 확인
✔ 목적
이 건물에서는 “월간 검색량(PC·모바일)” 데이터를 제공합니다.
즉,
“사람들이 이 키워드를 얼마나 찾는가?”
수요를 알려주는 핵심 키입니다.
🧭 2단계 — 네이버 검색 API 키 발급 (문서수 담당)
📍 사이트
✔ 해야 할 일
로그인 → 상단 Application → ‘애플리케이션 등록’
API 선택: 검색(Search)
Client ID, Client Secret 발급
메모해두기
✔ 목적
이 건물에서는 “블로그 문서수(total)” 데이터를 줍니다.
즉,
“이 키워드로 이미 글을 몇 개나 써놨는가?”
경쟁량을 알려주는 핵심 키입니다.
🧭 3단계 — API 키값을 파이썬 환경에 등록
보통 .env 로 넣지만 이번 실습은 코드 안에 바로 넣는 형태로 구성되어 있습니다.
NAVER_SEARCH_ACCESS_LICENSE_KEY = "발급받은 Access Key 입력"
NAVER_SEARCH_SECRET_KEY = "발급받은 Secret Key 입력"
NAVER_SEARCH_CUSTOMER_ID = "광고센터 Customer ID"
NAVER_BLOG_CLIENT_ID = "네이버 검색 API Client ID"
NAVER_BLOG_CLIENT_SECRET = "네이버 검색 API Secret"
이건 마치
“출입카드 번호를 내 프로그램에게 알려주는 과정”
이라고 생각하시면 됩니다.
🧭 4단계 — 파이썬 프로그램 실행
✔ 초보자도 한 번에 이해할 수 있게 전체 코드는 아래에 그대로 제공합니다.
import requests
import hmac
import hashlib
import base64
import time
import json
import urllib.parse
from collections import deque
import openpyxl
from openpyxl.utils import get_column_letter
# 네이버 검색광고 API 정보
NAVER_SEARCH_ACCESS_LICENSE_KEY = "01000000001f114edd83ad29eceaf97acf89cbcbc27283150884065a5467a4cb19efff08ee"
NAVER_SEARCH_SECRET_KEY = "AQAAAAAfEU7dg60p7Or5es+Jy8vC48WR8I2Y9hX4SYlEpORx2g=="
NAVER_SEARCH_CUSTOMER_ID = "4180063"
NAVER_API_BASE_URL = "https://api.naver.com"
# 네이버 검색 API (블로그 검색) 정보
NAVER_BLOG_CLIENT_ID = "O6AVg7D5tucjFH72rYJQ"
NAVER_BLOG_CLIENT_SECRET = "_TWdNmG_aN"
def generate_signature(secret_key: str, timestamp: str, method: str, request_uri: str) -> str:
"""
네이버 검색광고 API 요청에 필요한 시그니처를 생성합니다.
"""
message = f"{timestamp}.{method}.{request_uri}"
h = hmac.new(secret_key.encode('utf-8'), message.encode('utf-8'), hashlib.sha256)
return base64.b64encode(h.digest()).decode('utf-8')
def get_keyword_stats(access_key: str, secret_key: str, customer_id: str, hint_keywords: list) -> dict:
"""
지정된 키워드에 대한 통계 정보를 네이버 검색광고 API로부터 가져옵니다.
"""
request_uri = "/keywordstool"
method = "GET"
timestamp = str(int(time.time() * 1000))
signature = generate_signature(secret_key, timestamp, method, request_uri)
headers = {
"X-Timestamp": timestamp,
"X-API-KEY": access_key,
"X-Customer": customer_id,
"X-Signature": signature
}
params = {
"hintKeywords": ",".join(hint_keywords),
"showDetail": "1" # 상세 통계 정보 포함
}
url = f"{NAVER_API_BASE_URL}{request_uri}"
try:
response = requests.get(url, headers=headers, params=params)
response.raise_for_status() # HTTP 오류 발생 시 예외 발생
return response.json()
except requests.exceptions.HTTPError as err:
print(f"HTTP 오류 발생: {err}")
print(f"응답 내용: {response.text}")
return {}
except requests.exceptions.RequestException as err:
print(f"요청 오류 발생: {err}")
return {}
def get_document_count(keyword: str, client_id: str, client_secret: str) -> int:
"""
네이버 블로그 검색 API를 사용하여 특정 키워드에 대한 문서 수를 가져옵니다.
"""
encText = urllib.parse.quote(keyword)
url = f"https://openapi.naver.com/v1/search/blog?query={encText}&display=1"
headers = {
"X-Naver-Client-Id": client_id,
"X-Naver-Client-Secret": client_secret
}
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
data = response.json()
return data.get("total", 0)
except requests.exceptions.HTTPError as err:
print(f"HTTP 오류 발생 (문서수): {err}")
print(f"응답 내용 (문서수): {response.text}")
return 0
except requests.exceptions.RequestException as err:
print(f"요청 오류 발생 (문서수): {err}")
return 0
if __name__ == "__main__":
input_keywords_str = input("검색할 초기 키워드를 입력하세요 (쉼표로 구분): ")
initial_keywords = [kw.strip() for kw in input_keywords_str.split(',') if kw.strip()]
if not initial_keywords:
print("입력된 키워드가 없습니다. 프로그램을 종료합니다.")
else:
num_keywords_to_process = input("몇 개의 키워드를 조회하시겠습니까? (숫자를 입력하세요): ")
try:
num_keywords_to_process = int(num_keywords_to_process)
if num_keywords_to_process <= 0:
print("키워드 조회 개수는 양수여야 합니다. 프로그램을 종료합니다.")
exit()
except ValueError:
print("잘못된 숫자 형식입니다. 프로그램을 종료합니다.")
exit()
keyword_queue = deque(initial_keywords)
processed_keywords = set()
all_keyword_data = [] # 모든 키워드 데이터를 저장할 리스트
# 엑셀 워크북 및 시트 설정
workbook = openpyxl.Workbook()
sheet = workbook.active
sheet.title = "키워드 검색량"
# 헤더 작성
headers = ["키워드", "월간 PC 검색량", "월간 모바일 검색량", "총 월간 검색량", "문서수", "경쟁률"]
sheet.append(headers)
# 엑셀에 기록된 고유 키워드의 개수를 추적합니다.
keywords_recorded_count = 0
while keyword_queue and keywords_recorded_count < num_keywords_to_process:
current_keyword = keyword_queue.popleft()
if current_keyword in processed_keywords:
print(f"'{current_keyword}' 키워드는 이미 조회했거나 처리 중입니다. 건너킵니다.")
continue
processed_keywords.add(current_keyword) # 이 키워드는 이미 처리되었으므로 추가합니다 (API 호출)..
print(f"'{current_keyword}' 키워드의 검색량 정보를 가져옵니다...")
stats_data = get_keyword_stats(
NAVER_SEARCH_ACCESS_LICENSE_KEY,
NAVER_SEARCH_SECRET_KEY,
NAVER_SEARCH_CUSTOMER_ID,
[current_keyword]
)
if stats_data and "keywordList" in stats_data:
keyword_list = stats_data["keywordList"]
if isinstance(keyword_list, list):
for item in keyword_list:
rel_keyword = item.get("relKeyword", "N/A")
# 이 관련 키워드가 아직 엑셀에 기록되지 않았고, 목표 개수에 도달하지 않았다면 기록합니다.
if rel_keyword not in processed_keywords and keywords_recorded_count < num_keywords_to_process:
monthly_pc_qc_cnt = item.get("monthlyPcQcCnt", "N/A")
monthly_mobile_qc_cnt = item.get("monthlyMobileQcCnt", "N/A")
total_monthly_qc_cnt = "N/A"
try:
pc_qc = int(monthly_pc_qc_cnt) if monthly_pc_qc_cnt not in ["<10", "N/A"] else 0
mobile_qc = int(monthly_mobile_qc_cnt) if monthly_mobile_qc_cnt not in ["<10", "N/A"] else 0
total_monthly_qc_cnt = pc_qc + mobile_qc
if "<10" in [monthly_pc_qc_cnt, monthly_mobile_qc_cnt] and total_monthly_qc_cnt < 10:
total_monthly_qc_cnt = "<10"
elif total_monthly_qc_cnt == 0 and "<10" in [monthly_pc_qc_cnt, monthly_mobile_qc_cnt]:
total_monthly_qc_cnt = "<10"
except ValueError:
pass
# 문서수 조회
document_count = get_document_count(rel_keyword, NAVER_BLOG_CLIENT_ID, NAVER_BLOG_CLIENT_SECRET)
# 경쟁률 계산 (월간총검색량이 0이거나 '<10'인 경우 0으로 처리, 문서수가 0인 경우 N/A)
competition_rate = "N/A"
try:
total_qc_for_calc = 0
if isinstance(total_monthly_qc_cnt, int):
total_qc_for_calc = total_monthly_qc_cnt
elif total_monthly_qc_cnt == "<10":
total_qc_for_calc = 5 # <10인 경우 대략 5로 가정하여 계산
if total_qc_for_calc > 0 and document_count > 0:
competition_rate = round(document_count / total_qc_for_calc, 2)
elif document_count == 0:
competition_rate = "N/A" # 문서수가 없으면 경쟁률을 계산할 수 없음
elif total_qc_for_calc == 0:
competition_rate = "N/A" # 검색량이 없으면 경쟁률을 계산할 수 없음
except Exception as e:
print(f"경쟁률 계산 중 오류 발생: {e}")
competition_rate = "N/A"
print(f"키워드: {rel_keyword}")
print(f" 월간 PC 검색량: {monthly_pc_qc_cnt}")
print(f" 월간 모바일 검색량: {monthly_mobile_qc_cnt}")
print(f" 총 월간 검색량: {total_monthly_qc_cnt}")
print(f" 문서수: {document_count}")
print(f" 경쟁률: {competition_rate}")
print("-" * 30)
all_keyword_data.append([
rel_keyword,
monthly_pc_qc_cnt,
monthly_mobile_qc_cnt,
total_monthly_qc_cnt,
document_count,
competition_rate
])
sheet.append([rel_keyword, monthly_pc_qc_cnt, monthly_mobile_qc_cnt, total_monthly_qc_cnt, document_count, competition_rate])
processed_keywords.add(rel_keyword) # 엑셀에 기록되었으므로, 처리된 키워드로 추가합니다.
keywords_recorded_count += 1 # 엑셀에 기록된 키워드 개수 증가
# 추가로 탐색할 키워드가 더 필요하고, 이 키워드가 아직 큐에 없다면 큐에 추가합니다.
if keywords_recorded_count < num_keywords_to_process and rel_keyword not in keyword_queue:
keyword_queue.append(rel_keyword)
# 이 관련 키워드가 이전에 처리되지 않았지만, 이미 목표 개수에 도달했다면
# 엑셀에는 기록하지 않고, 향후 중복 처리를 막기 위해 processed_keywords에만 추가합니다.
elif rel_keyword not in processed_keywords:
processed_keywords.add(rel_keyword)
else:
print("API 응답의 'keywordList'가 리스트 형식이 아닙니다.")
print(json.dumps(stats_data, indent=2, ensure_ascii=False))
elif stats_data:
print("API 응답 형식이 예상과 다릅니다. 원시 응답:")
print(json.dumps(stats_data, indent=2, ensure_ascii=False))
else:
print(f"'{current_keyword}' 키워드 검색량 정보를 가져오는데 실패했습니다.")
# 엑셀 파일 저장
first_keyword = initial_keywords[0]
excel_file_name = f"{first_keyword}.xlsx"
try:
workbook.save(excel_file_name)
print(f"키워드 검색량 정보가 '{excel_file_name}' 파일로 저장되었습니다.")
except PermissionError:
print(f"오류: '{excel_file_name}' 파일에 접근할 수 없습니다. 파일이 다른 프로그램에서 열려 있는지 확인하고 닫아주세요.")
except Exception as e:
print(f"엑셀 파일 저장 중 알 수 없는 오류가 발생했습니다: {e}")
이 코드는 다음과 같은 일을 자동으로 합니다:
① 씨앗 키워드 입력
"창문바람막이"
"AI 영어"
"겨울 외풍"
같은 시작 키워드를 넣습니다.
② 검색광고 API 호출 → 검색량 가져오기
PC 검색량
모바일 검색량
연관 키워드 수집
③ 검색 API 호출 → 문서수(total) 가져오기
경쟁 글 개수 조회
④ 경쟁률 계산
경쟁률 = 문서수 ÷ 검색량
→ 낮을수록 좋은 키워드
⑤ 키워드 BFS 확장
"창문바람막이" →
"창틀바람막이" →
"현관문방풍" →
"방문문풍지" …
이런 식으로 자동 확장됨.
⑥ 엑셀로 자동 저장
모든 결과를창문바람막이.xlsx처럼 자동으로 저장합니다.
🍀 5단계 — 실제 예시 (문틈바람막이.xlsx로 계산)
실제로 프로그램을 돌려 나온 결과는 다음과 같습니다.
키워드
총 월간 검색량
문서수
경쟁률 (문서수 ÷ 검색량)
창문바람막이
8,600
39,304
4.57
현관문방풍
2,460
9,910
4.03
방문문풍지
520
14,360
27.62
창틀바람막이
4,120
6,871
1.67 (최고)
외풍차단시공
700
106,592
152.27 (최악)
🔍 결과 해석
🥇 최고의 키워드: 창틀바람막이 (1.67)
검색량 대비 문서수가 매우 적음
상위노출 가능성 가장 크다
“문틈/바람막이” 분야의 핵심 골든키워드
🥈 안정적인 키워드
현관문방풍 – 4.03
창문바람막이 – 4.57
적당한 검색량 + 안정적인 경쟁 수준
🔥 피해야 할 키워드
방문문풍지 – 27.62
외풍차단시공 – 152.27
검색량 대비 문서수가 너무 많음 → 레드오션
🟦 결과와 배운 점
① 검색량만 보는 분석은 반쪽짜리다
문서수(경쟁량)까지 함께 보면 키워드의 질이 완전히 다르게 보인다.
② 경쟁률이 낮을수록 상위 노출 가능성이 커진다
이게 전체 프로그램의 핵심 공식.
경쟁률 = 문서수 ÷ 검색량
(낮을수록 좋음)
③ BFS(키워드 주변을 동그랗게 넓혀가며 탐색) 방식이 최고의 키워드 채굴법
연관 키워드를 자동으로 확장해서
예상치 못한 “숨은 황금 키워드”도 계속 발견됨.
🟦 도움 받은 글
네이버 검색광고 API 공식 문서
네이버 Developers 검색 API 문서
오픈소스 BFS 탐색 구조 참고
커서를 통한 오류 디버깅 및 로직 정교화