Marketing Story

텍스트 마이닝을 활용한 시장 분석과 연관 검색어 분석

Jimmy Butler 2024. 10. 3. 00:16

 

마케터라면 누구나 한번쯤은 구글트렌드, 네이버 데이터랩과 같은 사이트를 통해 키워드의 쿼리수 동향을 살펴본 적이 있을 것이다. 시간 흐름에 따른 키워드의 쿼리 추이를 그래프로 한눈에 살펴볼 수 있어 활용도가 높은 사이트다.

 

또한, 네이버 검색광고의 키워드 도구를 통해 메인키워드와 연관된 세부키워드의 월별 검색량, 최근기간 파워링크 평균 1위 입찰가 역시 확인이 가능하다.

 

이렇듯 키워드를 분석하고 추출해야 할 때가 꽤나 있는데, 이때 사이트 크롤링을  검색 결과 내용을 크롤링 하여 SEO 현황과 연관 검색어, 간단한 시장 분석까지 함께 진행하면 보다 나은 조사를 진행할 수 있다.

 

예를들어 최근 특정 산업군에서 자주 언급되는 키워드는 무엇인지? 혹은, 체험단 인플루언서 모집을 진행하려고 하는데 경쟁사는 블로그/카페 컨텐츠에 주로 어떤 내용을 담아서 포스팅했는지? 네이버 쇼핑에 제품명을 검색했을 때, 따라오는 연관검색어에는 어떤 것들이 있는지? 다양한 경우에 크롤링을 통해 텍스트 마이닝을 구현해볼 수 있다.

 

 

네이버에 '소비자물가' 검색 시 노출되는 기사

 

1. 네이버 키워드에 '소비자물가'라는 키워드를 검색했을 때 노출되는 기사들이다. 네이버 기사 헤드라인을 크롤링하여 아래와 같이 텍스트 마이닝해볼 수 있다.

크롤링된 네이버 기사 헤드라인 상위 50개

 

네이버 기사 헤드라인으로 텍스트마이닝한 결과

 

 

코드를 공유하자면 아래와 같다. 아래 코드는 챗GPT를 활용하여 구현한 코드이니 파이썬을 할 줄 모를지라도 한 번 시도해 보는 것도 좋을 것 같다.

import requests
from bs4 import BeautifulSoup
from collections import Counter
import pandas as pd
from wordcloud import WordCloud
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties

# 한국어 불용어 리스트
korean_stopwords = [
    '이', '가', '은', '는', '의', '와', '과', '을', '를', '에', 
    '에서', '에게', '가서', '아니', '하지만', '그러나', '때문에', 
    '그', '그것', '이런', '저런', '어떤', '모든', '모두', '각', 
    '아', '도', '로', '으로', '라', '마', '다', '하', '들'
]

# User-Agent 헤더 추가
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36'
}

# 검색할 키워드
keyword = "소비자물가"

# 키워드 텍스트를 저장할 리스트
all_texts = []

# Naver 뉴스 검색 결과 크롤링 함수 (페이지 넘기기)
def get_news_titles(keyword, page=1):
    url = f"https://search.naver.com/search.naver?query={keyword}&where=news&start={(page-1)*10+1}"
    response = requests.get(url, headers=headers)
    soup = BeautifulSoup(response.text, 'html.parser')

    # 뉴스 기사 제목 추출
    articles = soup.find_all('a', {'class': 'news_tit'})

    if not articles:
        return None  # 기사가 없으면 None 리턴

    for article in articles:
        title = article.get_text()
        all_texts.append(title)

    return True

# 페이지를 끝까지 크롤링 (상위 50개 기사만)
for page in range(1, 6):  # 5페이지까지 크롤링 (한 페이지당 10개 기사)
    if not get_news_titles(keyword, page):
        break

# 크롤링한 기사 제목 출력
if not all_texts:
    print(f"'{keyword}'에 대한 기사를 찾을 수 없습니다.")
else:
    print(f"'{keyword}'에 대한 상위 50개 뉴스 기사 헤드라인:")
    for idx, title in enumerate(all_texts, 1):
        print(f"{idx}. {title}")

# 텍스트 전처리 및 단어 빈도 분석
cleaned_texts = ' '.join(all_texts)
words = [word for word in cleaned_texts.split() if word not in korean_stopwords]

# 단어 빈도 계산
word_counts = Counter(words)

# 결과를 WordCloud로 시각화
wordcloud = WordCloud(
    font_path='C:/Windows/Fonts/malgun.ttf',  # 한글 글꼴 경로
    width=800,
    height=400,
    background_color='white',
    colormap='tab20'
).generate_from_frequencies(word_counts)

# 그래프 제목 폰트 설정
title_font = FontProperties(fname='C:/Windows/Fonts/malgun.ttf', size=16)

# 그래프 출력
plt.figure(figsize=(12, 6))
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off')  # 축 제거
plt.title(f"'{keyword}'", fontproperties=title_font)
plt.show()

 

 

2. 셀리니움 라이브러리를 통해 크롤링 하는 방법도 있다. 다음은 셀리니움 라이브러리를 통해 네이버 쇼핑에서 키워드 검색 시 표시되는 연관 검색어를 크롤링한 결과다.

네이버 쇼핑에서 '조거팬츠' 검색 시 표시되는 연관 검색어
네이버 쇼핑에서 '카고바지' 검색 시 표시되는 연관 검색어
크롤링된 연관 검색어
네이버 쇼핑 연관 검색어 텍스트 마이닝 결과

 

해당 코드 역시 챗GPT로 구현하였다.

 

import time
from selenium import webdriver
from seleniuhttp://m.webdriver.chrome.service import Service
from seleniuhttp://m.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from collections import Counter
from wordcloud import WordCloud
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties

# 한국어 불용어 리스트
korean_stopwords = [
    '이', '가', '은', '는', '의', '와', '과', '을', '를', '에', 
    '에서', '에게', '가서', '아니', '하지만', '그러나', '때문에', 
    '그', '그것', '이런', '저런', '어떤', '모든', '모두', '각', 
    '아', '도', '로', '으로', '라', '마', '다', '하', '들','...'
]

# Chrome WebDriver 설정
service = Service(ChromeDriverManager().install())
browser = webdriver.Chrome(service=service)

# 검색할 키워드 리스트
keywords = ["조거팬츠", "카고바지"]  # 원하는 키워드 추가

# 모든 연관 검색어 저장할 리스트
all_related_keywords = []

for keyword in keywords:
    # 네이버 쇼핑 페이지로 이동
    browser.get('https://shopping.naver.com/home')
    browser.implicitly_wait(15)
    
    # 검색창에 키워드 입력
    search = browser.find_element(By.CSS_SELECTOR, 'input._searchInput_search_text_3CUDs')
    search.send_keys(keyword)
    
    # 검색 버튼 클릭
    search_button = browser.find_element(By.CSS_SELECTOR, 'button._searchInput_button_search_1n1aw')
    search_button.click()
    
    time.sleep(3)  # 페이지 로딩 대기
    
    # 더보기 버튼 클릭 (만약 있으면)
    try:
        more_button = browser.find_element(By.CSS_SELECTOR, 'button.relatedTags_btn_more__Fdsm1')
        more_button.click()
        time.sleep(2)  # 더보기 클릭 후 페이지 로딩 대기
    except Exception as e:
        print(f"'{keyword}'에 대한 더보기 버튼을 찾을 수 없습니다. 에러: {e}")

    # 연관 검색어만 필터링
    related_keywords_elements = browser.find_elements(By.CSS_SELECTOR, 'li._nlog_click._nlog_impression_element')

    for element in related_keywords_elements:
        # 연관 검색어는 'data-shp-inventory' 속성이 'rel_tag'로 설정됨
        if element.get_attribute('data-shp-inventory') == 'rel_tag':
            related_keyword = element.text.strip()
            if related_keyword:  # 비어 있지 않으면 추가
                all_related_keywords.append(related_keyword)
        else:
            continue

# 브라우저 종료
browser.quit()

# 크롤링한 연관 검색어 출력
print("전체 연관 검색어:")
for keyword in all_related_keywords:
    print(keyword)

# 텍스트 마이닝 맵 생성
# 불용어 제거 후 단어 빈도 계산
cleaned_texts = ' '.join(all_related_keywords)
words = [word for word in cleaned_texts.split() if word not in korean_stopwords]
word_counts = Counter(words)

# 워드클라우드 생성
wordcloud = WordCloud(
    font_path='C:/Windows/Fonts/malgun.ttf',  # 한글 글꼴 경로
    width=800,
    height=400,
    background_color='white',
    colormap='tab20'
).generate_from_frequencies(word_counts)

# 그래프 제목 폰트 설정
title_font = FontProperties(fname='C:/Windows/Fonts/malgun.ttf', size=16)

# 워드클라우드 출력
plt.figure(figsize=(12, 6))
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off')  # 축 제거
plt.title(f"'{', '.join(keywords)}' 연관 검색어", fontproperties=title_font)
plt.show()

 

 

3. 다음은 연관 검색어 네트워크 맵이다. 네이버에 특정키워드를 검색하면 연관검색어가 표시되는 경우가 있다. 연관검색어를 클릭하면 또 다른 연관 검색어가 표시되는데 이를 크롤링하여 연관 검색어 네트워크 맵을 구현할 수 있다. 

 

네이버에 '흑백요리사' 검색 시 표시되는 연관 검색어

 

연관 검색어 '흑백요리사 공개시간' 클릭 시 표시되는 연관 검색어

 

 

'흑백요리사' 키워드 검색 시 노출되는 연관검색어와 그 키워드들의 연관 검색어를 반복하여 크롤

 

텍스트 마이닝을 통해 구현된 연관 검색어 네트워크 맵

 

*연관 검색어 네트워크 맵 텍스트 마이닝 구현 코드

import requests
from bs4 import BeautifulSoup
import networkx as nx
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
from matplotlib import rc
import random
import matplotlib.colors as mcolors

# 한글 폰트 설정 (맑은 고딕 폰트 사용)
font_path = "c:/Windows/Fonts/malgun.ttf"  # 폰트 경로
font_name = fm.FontProperties(fname=font_path).get_name()
rc('font', family=font_name)  # 폰트 설정

# 1. 데이터 수집 (연관 검색어 크롤링)
def get_related_keywords(keyword):
    url = f'https://search.naver.com/search.naver?query={keyword}'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36'
    }
    response = requests.get(url, headers=headers)
    soup = BeautifulSoup(response.text, 'html.parser')
    
    related_keywords = []
    
    # 연관 검색어 추출
    for item in soup.select('a.keyword .tit'):
        related_keywords.append(item.text.strip())
    
    return related_keywords

# 2. 키워드 간의 관계 수집 (네트워크 그래프 생성)
def build_keyword_network(start_keyword, depth=40):
    graph = nx.Graph()
    keywords_to_visit = [(start_keyword, 0)]  # (keyword, depth) 형식으로 저장
    visited_keywords = set()
    keyword_depths = {}  # 노드의 깊이 저장

    while keywords_to_visit and depth > 0:
        current_keyword, current_depth = keywords_to_visit.pop()
        
        if current_keyword in visited_keywords:
            continue

        visited_keywords.add(current_keyword)
        keyword_depths[current_keyword] = current_depth  # 깊이 기록
        related_keywords = get_related_keywords(current_keyword)

        for related_keyword in related_keywords:
            graph.add_edge(current_keyword, related_keyword)
            if related_keyword not in visited_keywords:
                keywords_to_visit.append((related_keyword, current_depth + 1))  # 깊이를 추가하여 저장

        depth -= 1

    return graph, visited_keywords, keyword_depths  # 방문한 키워드와 깊이 반환

# 3. 네트워크 맵 시각화 (디자인 맞춤)
def draw_network(graph, start_keyword, keyword_depths):
    plt.figure(figsize=(14, 10))  # 전체 맵 크기
    pos = nx.spring_layout(graph, k=0.5)  # 노드 배치

    # 노드 색상 및 크기
    pastel_colors = ['#FFB3BA', '#FFDFBA', '#FFFFBA', '#BAFFC9', '#BAE1FF', '#FFABAB']
    node_colors = [random.choice(pastel_colors) for _ in graph]  # 노드 색상 랜덤 적용

    node_sizes = [len(graph[node]) * 400 + 200 if node == start_keyword else (len(graph[node]) * 400 + 100) for node in graph]  # 크기 조정

    # 엣지 두께
    edge_widths = [0.5 for _ in range(len(graph.edges()))]  # 엣지 두께를 얇게 설정
    nx.draw(graph, pos, with_labels=False, node_size=node_sizes, node_color=node_colors, edge_color='gray', width=edge_widths)  # 엣지 색상 변경

    # 글씨 색상 그라데이션 설정 (깊이에 따른 색상 변경)
    cmap = mcolors.LinearSegmentedColormap.from_list('depth_gradient', ['black', '#D3D3D3'])  # 검정색에서 회색으로 그라데이션
    max_depth = max(keyword_depths.values())  # 최대 깊이
    labels = {node: node for node in graph.nodes()}
    
    for node, (x, y) in pos.items():
        # 노드별 글씨 크기 및 색상 조정
        font_size = 16 if node == start_keyword else (12 if len(graph[node]) > 2 else 10)  # 시작 키드는 글씨 크기 키움
        depth_ratio = keyword_depths.get(node, 0) / max_depth if max_depth > 0 else 0  # 현재 노드의 깊이 비율, 키가 없을 경우 기본값 0
        text_color = cmap(depth_ratio)  # 깊이에 따른 색상 적용
        plt.text(x, y, node, fontfamily=font_name, fontsize=font_size, color=text_color, ha='center', va='center')

    # 제목 설정
    plt.title("연관 검색어 네트워크 맵", fontsize=20, fontproperties=fm.FontProperties(fname=font_path))
    plt.axis('off')  # 축 제거
    plt.show()

# 4. 키워드 리스트 출력
def print_keywords(keywords):
    print("연관 검색어 리스트:")
    for keyword in keywords:
        print(f"- {keyword}")

# 5. 시작 키워드 설정 및 실행
start_keyword = '흑백요리사'  # 원하는 키워드로 변경 가능
keyword_graph, visited_keywords, keyword_depths = build_keyword_network(start_keyword)
draw_network(keyword_graph, start_keyword, keyword_depths)
print_keywords(visited_keywords)  # 방문한 키워드 리스트 출력


4. 마지막으로 네이버 블로그, 카페 탭의 글 제목을 크롤링 하는 방법이다. 블로그 포스팅 혹은 체험단 진행 시 원고 작성에 참고하면 좋을 것 같다.

 

네이버 블로그, 카페 탭의 글 제목
'고구마간식' 키워드 검색 시 네이버 블로그, 카페 상위 30개 글 제목 크롤링
블로그, 카페 글 제목으로 언급된 빈도로 구현한 텍스트 마이닝

 

 

*네이버 블로그, 카페 탭의 각 상위 30개 글 제목 크롤링 코드이며, 쇼핑 연관 검색어 크롤링과 동일하게 셀리니움 라이브러리를 사용했다.

import time
from selenium import webdriver
from seleniuhttp://m.webdriver.chrome.service import Service
from seleniuhttp://m.webdriver.common.by import By
from seleniuhttp://m.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
from collections import Counter
from wordcloud import WordCloud
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties

# 한국어 불용어 리스트
korean_stopwords = [
    '이', '가', '은', '는', '의', '와', '과', '을', '를', '에', 
    '에서', '에게', '가서', '아니', '하지만', '그러나', '때문에', 
    '그', '그것', '이런', '저런', '어떤', '모든', '모두', '각', 
    '아', '도', '로', '으로', '라', '마', '다', '하', '들', '...'
]

# Chrome WebDriver 설정
service = Service(ChromeDriverManager().install())
browser = webdriver.Chrome(service=service)

# 검색할 키워드
keyword = "고구마간식"

# 블로그와 카페의 글 제목을 저장할 리스트
blog_titles = []
cafe_titles = []

try:
    # 네이버 검색 페이지로 이동
    browser.get('https://www.naver.com')
    
    # 검색창에 키워드 입력
    search = WebDriverWait(browser, 10).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, 'input#query'))
    )
    search.send_keys(keyword)

    # 검색 버튼 클릭
    search_button = browser.find_element(By.CSS_SELECTOR, 'button.btn_search')
    search_button.click()

    # 페이지 로딩 대기
    time.sleep(3)

    # 블로그 탭으로 이동하여 제목 크롤링
    blog_tab = WebDriverWait(browser, 10).until(
        EC.element_to_be_clickable((By.LINK_TEXT, '블로그'))
    )
    blog_tab.click()
    time.sleep(3)

    # 블로그 제목 크롤링
    blog_elements = browser.find_elements(By.CSS_SELECTOR, 'div.title_area > a.title_link')
    for element in blog_elements[:30]:  # 상위 30개 제목만 크롤링
        blog_titles.append(element.text)

    # 카페 탭으로 이동하여 제목 크롤링
    cafe_tab = WebDriverWait(browser, 10).until(
        EC.element_to_be_clickable((By.LINK_TEXT, '카페'))
    )
    cafe_tab.click()
    time.sleep(3)

    # 카페 제목 크롤링
    cafe_elements = browser.find_elements(By.CSS_SELECTOR, 'div.title_area > a.title_link')
    for element in cafe_elements[:30]:  # 상위 30개 제목만 크롤링
        cafe_titles.append(element.text)

finally:
    # 브라우저 종료
    browser.quit()

# 크롤링한 블로그와 카페 제목 출력
print("블로그 제목:")
for title in blog_titles:
    print(title)

print("\n카페 제목:")
for title in cafe_titles:
    print(title)

# 텍스트 마이닝 맵 생성
all_titles = blog_titles + cafe_titles

# 불용어 제거 후 단어 빈도 계산
cleaned_texts = ' '.join(all_titles)
words = [word for word in cleaned_texts.split() if word not in korean_stopwords]
word_counts = Counter(words)

# 워드클라우드 생성
if word_counts:  # 빈도수가 0이 아닐 경우에만 워드클라우드 생성
    wordcloud = WordCloud(
        font_path='C:/Windows/Fonts/malgun.ttf',  # 한글 글꼴 경로
        width=800,
        height=400,
        background_color='white',
        colormap='tab20'
    ).generate_from_frequencies(word_counts)

    # 그래프 제목 폰트 설정
    title_font = FontProperties(fname='C:/Windows/Fonts/malgun.ttf', size=16)

    # 워드클라우드 출력
    plt.figure(figsize=(12, 6))
    plt.imshow(wordcloud, interpolation='bilinear')
    plt.axis('off')  # 축 제거
    plt.title(f"'{keyword}' 관련 제목들", fontproperties=title_font)
    plt.show()
else:
    print("워드클라우드를 생성할 수 있는 제목이 없습니다.")

 

 

 

 

이렇게 텍스트 마이닝을 통해 보다 쉽고 빠르게 키워드 조사가 가능하다. 또한 파이썬 활용 능력이 부족하다 하더라고 'F12' 개발자 도구를 통해 크롤링 하고자 하는 영역의 HTML 엘리먼트 속성을 확인한 뒤, 챗 GPT에게 코드 구현을 요청하면 쉽게 파이썬 코드를 완성할 수 있다.