본문 바로가기
  • 사진, 커피, 그리고 광고를 좋아합니다
마케팅/빅데이터

크롤링과 텍스트 마이닝을 통해 본 만우절 커피 키워드

by 왓섭마이트 2021. 4. 4.

안녕하세요. 피 대신 에스프레소가 흐르고 광고를 좋아하는 디지비션입니다:)

디시 인사이드 차, 음료 갤러리는 커피 트렌드를 한국에서 가장 잘 볼 수 있는 공간입니다. 타 커뮤니티에 비해 커피에 관한 글이 많이 올라오고 외국 최신 자료를 가져오는 등 커피, 그 자체에 열정적인 모습을 보실 수 있습니다.

차, 음료 갤러리에서 아쉬운 점은 추천수가 높은 글은 볼 수 있지만, 조회수가 높은 글을 찾아보는 것은 힘듭니다. 마침, 4월 1일 만우절이 있었고, 저는 앞부분의 아쉬움을 해결하고, 또한 어떤 글이 인기 있는지 분석하고자 하여 크롤링을 진행했고, 간단한 텍스트 마이닝을 진행했습니다. 

오늘은 이에 관한 설명을 하겠습니다. 마지막에 깃 주소도 공유할 테니, 잘 참고하시어 원하는 자료만 뽑아낼 수 있었으면 좋겠습니다!


차, 음료 갤러리의 모습.

1. 크롤링

크롤링을 진행하기 전에 우선 사이트의 구조를 파악해야 합니다. 이를 파악하기 위해선 주소 칸의 규칙을 살펴보시면 됩니다.

디시 인사이드의 주소는 gall.dcinside.com/board/lists +? id=(갤 이름)으로 되어 있는 것을 확인하실 수 있습니다. 

주소 분석은 간단히 끝낼 수 있었고, 이제, 원하는 정보를 크롤링하기 위해 F12 개발자 도구를 실행해 개발 구조를 파악하면 됩니다.

저는 1. 만우절 하루 동안 쓰인 글 개수는 몇 개인지
2. 만우절 하루 동안 얼마나 많은 사람이 글을 작성했는지
3. 만우절 가장 글을 많이 쓴 사람은 누구인지
4. 추천이 가장 높았던 글은 무엇인지
5. 어그로 키워드는 무엇이었는지
6. 하루 동안 가장 인기 있던 키워드는 무엇이었는지
알아보고자 했습니다. 

이를 위해 저는 제목, 글쓴이, 날짜, 조회수, 추천수만을 가져오면 됐습니다. 

개발도구를 켠후 인스펙터로 제목의 구조를 확인.

F12를 누른 후, ctrl + shift + c를 누르면 인스펙터를 실행할 수 있습니다. 이를 이용해 내가 알고 싶은 부분의 구조를 간단히 살펴볼 수 있습니다. 지금 알고 싶은 것은 제목의 구조입니다. 살펴보니 a태그인 게 눈에 띕니다. 이렇게 각자 원하는 부분의 구조를 파악한 후, 크롤링을 시작하면 편합니다.

1-1. BeatifulSoup 라이브러리

크롤링을 할 수 있는 라이브러리는 많이 존재합니다. 그중 저는 Beautiful Soup과 Selenium 라이브러리를 사용합니다.

Beautiful Soup의 경우는 간단히 구조를 파악 후 화면 내에 있는 정보만을 가져올 수 있습니다. 이에 반해 Selenium은 우리가 인터넷 서핑을 하는 것처럼 화면을 클릭하고 정보를 가져옵니다. 복잡한 구조의 사이트에선 Selenium을 쓰는 것이 더 좋지만, 속도가 느리기 때문에 일반적인 (DC inside 같은) 사이트에서는 Beautiful Soup이 적합합니다.

우선, 라이브러리를 불러옵니다.

import pandas as pd
from bs4 import BeautifulSoup
import requests
import warnings
warnings.filterwarnings(action='ignore')

판다스로 데이터 프레임을 만들고, BeautifulSoup으로 크롤링을 불러오겠습니다. 그리고 뷰티플 슙과 세트 격인 request함수도 불러와주시면 됩니다. 마지막으로 긴 워닝 메시지를 피하기 위해 경고 무시를 실행시킵니다.

#메인 페이지 설정
DC_URL = "https://gall.dcinside.com/board/lists"

#크롤링 페이지 저장 형식 정리 (pandas)
list_index = ['제목', '글쓴이', '날짜', '조회수', '추천수']
list =[]

메인 페이지 개별 갤러리 주소 전 DC 인사이드의 대략적인 주소를 베이스로 작업을 시작하겠습니다. (이러면 나중에 다른 갤러리를 이용하는 것이 쉽고 일을 더 안 해도 됩니다.)

List 목록을 만들어 우리가 추출할 형식에 맞춰 인덱스를 지정해 줍니다. (마치 엑셀 맨 첫 칸에 입력하는 것과 같습니다)

#크롤링 페이지 수 정의
for num in range(1, 50):
    
#에러처리
    try:
        params = {'id': 'tea','page': f'{num}' }
        headers = { '개발자 도구속 자신의 헤더를 집어 넣는다'}
        resp = requests.get(DC_URL, params=params, headers=headers)
        soup = BeautifulSoup(resp.content, 'html.parser')
        contents = soup.find('tbody').find_all('tr')
        page_size = len(contents)
        
        for i in contents:
            line = []
            try:
                new_dict = {}
                
                #제목추출
                title_tag = i.find('a')
                title = title_tag.text
                line.append(title)
                
                #글쓴이 추출
                writer_tag = i.find('td', class_='gall_writer ub-writer').find('span', class_='nickname')
                try:
                    if writer_tag is not None:
                        writer = writer_tag.text
                        line.append(writer)
                        
                    else:
                        line.append('없음')
                except:
                    line.append('없음')
                
                # 날짜 추출 
                date_tag = i.find('td', class_='gall_date')
                date_dict = date_tag.attrs
                try:
                    if len(date_dict) is 2:
                        line.append(date_dict['title'])
    
                    else:
                        line.append(date_tag.text)
                except:
                    continue
                
                #조회수 추출
                views_tag = i.find('td', class_='gall_count')
                views = views_tag.text
                line.append(views)
                
                #추천수 추출
                recommend_tag = i.find('td', class_='gall_recommend')
                recommend = recommend_tag.text
                line.append(recommend)
                
                #리스트 파일로 저장
                list.append(line)
            except:
                continue
    except:
        continue
            
    resp.close()
    
    #데이터 저장처리 CSV
    df = pd.DataFrame(list, columns=list_index)
    df.to_csv('만우절 크롤링.csv', encoding = 'utf-8-sig')
    df.head()

리퀘스트로 주소를 요청하고 이를 부티풀 수프 라이브러리로 보냅니다. 그 후 우리가 찾고자 하는 (아까 찾아 놓은 주소)를 찾아 달라는 요청을 for문을 통해 진행하면 됩니다. 

저는 마지막에 csv파일로 저장해 이 작업이 끝나고도 엑셀로 볼 수 있도록 저장하였습니다. 

2. 데이터 정리 
2-1. 만우절 데이터만 추리기

크롤링했을 시 만우절날 만 추려 크롤링하는 것보단 DC 인사이드에서는 대략적인 페이지 까지만을 정해 놓고 후에 원하는 날짜를 찾는 것이 편합니다. 

df_sample_01 = df[df["날짜"].str.contains('2021-04-01')] #만우절 부분만 선정
df_sample_01.head()

문자열에 2021년 4월 1일 부분이 포함된 부분을 따로 다시 변수 지정해줍니다. ( str.contains( ) )

#조회수 타입 문자열인지 확인
df_sample_01["조회수"].dtypes

조회수의 데이터 타입을 확인해보니 숫자로 되어 있지 않아 숫자로 변경해주어야 합니다. 그렇지 않다면, 99가 111보다 높은 숫자로 인식하게 됩니다. (문자처럼 취급하게 됩니다.)

#조회수 시리즈 타입 숫자(int)로 변환
df_sample_02 = df_sample_01.astype({'조회수': int, '추천수' : int})

astype을 이용해 숫자 부분을 모두 int로 바꿔줍니다.

2-2. 유동 지우기

DC 인사이드는 로그인을 하지 않고도 글을 남길 수 있습니다. 대부분 ㅇㅇ라는 닉네임을 사용합니다. 이를 지워 노이즈를 없애겠습니다. (정확한 식별이 불가능해 제거하고 진행했습니다)

#유동 지우기
df_sample_03 = df_sample_02[df_sample_02["글쓴이"] != 'ㅇㅇ']
df_sample_04 = df_sample_03.sort_values(by=["조회수"], ascending=False)

3. 만우절 글 분석

처음 제가 알고 싶었던 부분. 1. 만우절 하루 동안 쓰인 글 개수는 몇 개인지 2. 만우절 하루 동안 얼마나 많은 사람이 글을 작성했는지 를 알아보겠습니다.

df_sample_01.describe()

describe 함수를 사용하면 간단히 해결할 수 있습니다. 

describe 함수는 데이터 프레임의 내용을 요약해 설명해줍니다. count는 데이터 프레임 내의 개수를 알려주고, unique는 성질이 다른 것들의 갯수를 알려줍니다. 이를 활용해 1번과 2번의 답을 말하면,

만우절 하루 동안 쓰인 글은 371개이고, 하루 동안 글을 쓴 사람은 105명입니다. (unique 부분 글쓴이에서 유동(ㅇㅇ)을 뺀 숫자) 

3-1. 만우절 가장 글을 많이 쓴 사람은 누구 일까?

그렇다면, 만우절에 글을 가장 많이 쓴 사람은 누구일까라는 의문점이 들었습니다. 이는 간단히 seaborn의 countplot을 이용하며 쉽게 해결됐습니다. 하지만, 내림차순으로 표를 만들고 싶었지만, 이 부분이 되지 않아 표를 도식화하기 전, 우선 내림차순으로 데이터를 변형해 그래프를 그려주었습니다.

#시각화 라이브러리
import seaborn as sns
import matplotlib.pyplot as plt

from collections import Counter

seaborn라이브러리를 실행시킨 후,

plt.figure(figsize=(50, 300))
plt.rc('font', size=40 )
#내림차순으로 넣는 방법 = order 부분 활용림차순으로 넣는 방법 = order 부분 활용
count_origin = df_sample_04["글쓴이"].value_counts()
g = sns.countplot(y = '글쓴이', data = df_sample_04, order = df_sample_04 [ '글쓴이' ]. value_counts (). index)
for i in range(count_origin.shape[0]):
    g.text(y=i, x=count_origin[i], s=count_origin[i], 
           horizontalalignment='center')
plt.title("만우절 가장 신난 사람")
plt.xticks(rotation = - 45 )
plt.show()

그 결과 이렇게 결과가 나오게 됩니다. seaborn은 시각화를 하기에 가장 예쁜 결괏값을 보여줍니다. (엑셀을 이용하지 않고 파이썬을 이용하는 이유 중 하나이기도 합니다!)

3-2. 가장 추천수가 높았던 글은 무엇일까?

추천수가 높다는 것은 그만큼 갤러리의 공감을 많이 샀다는 말이 됩니다. 이를 확인하는 방법은 쉽습니다. 그저 엑셀처럼 내림차순으로 나타내면 됩니다. 저는 1위부터 10위까지만 알고 싶었습니다.

df_sample_04.sort_values(by=["추천수"], ascending=False)[0:10]

3-3. 키워드 분석 (텍스트 마이닝, konlpy)

이젠 가장 영향력이 있었던 글의 키워드를 알고 싶었습니다. 저는 디씨 인사이드를 보면서 오래된 글이 꼭 조회수가 높지 않다는 것을 보았고, 이것은 특정 키워드에 사람들이 의식해 그 글에 들어간다는 생각을 하게 되었습니다. 이를 검증하기 위해선 키워드 분석이 필요했습니다.

키워드 분석을 하기 위해선 문장을 형태소(뜻을 가진 가장 작은 단위)로 나누어 주는 단계가 필요합니다. 이를 위해선 자바를 파이썬에서 돌릴 수 있게 하는 프로그램을 설치해야 하는데 제 컴퓨터에선 시도해도 되지 않아 구글 코랩을 사용해 진행했습니다. 

#텍스트 마이닝 라이브러리
!pip install nltk
from nltk.tokenize import word_tokenize
!apt-get install g++ openjdk-7-jdk
!apt-get install python3-dev; pip3 install konlpy

from konlpy.tag import Kkma       ; kkma = Kkma()
from konlpy.tag import Hannanum   ; hannanum = Hannanum()
from konlpy.tag import Okt        ; t = Okt()

from konlpy.tag import *

구글 코랩에서 위와 같은 코드를 실행해줍니다. 형태소를 나눌 때 쓰는 라이브러리는 konlpy라는 것을 사용합니다. 

 

KoNLPy: 파이썬 한국어 NLP — KoNLPy 0.4.3 documentation

KoNLPy: 파이썬 한국어 NLP KoNLPy(“코엔엘파이”라고 읽습니다)는 한국어 정보처리를 위한 파이썬 패키지입니다. 설치법은 이 곳을 참고해주세요. NLP를 처음 시작하시는 분들은 시작하기 에서 가

konlpy-ko.readthedocs.io

(자세한 내용은 대표 홈페이지에서 확인하실 수 있습니다. 자세한 설명이 적혀 있습니다.)

데이터 프레임 제목 단에 담긴 내용을 리스트로 옮겨주어야 형태소로 분리가 가능합니다.

#제목 단 리스트로 변환
df_sample_05 = df_sample_04.astype({'제목': str})
list_df_sample_05 = df_sample_05['제목'][0:100].values.tolist()
#list_df_sample_05

다음, 형태소 단위로 쪼개 줍니다.

morph_list = []
for i in list_df_sample_05:
    morphs = t.morphs(i)
    for morph in morphs:
        morph_list.append(morph)

그리고 불용어(뜻이 없는 단어들)를 처리해주는 가정을 거친 뒤, 가장 많이 나오는 키워드 5개를 찾아보겠습니다.

#불용어 처리
stop_words = [')','?','1','"(', '_', ')/','\n','.',',', '<','!','(','(', '??','..', '4', '|', '>', '?(', '"…', '#', '&', '・', "']",'.',' ','/',"'",'’','”','“','·', '[','!','\n','·','‘','"','\n ',']',':','…',')','(','-', 'nan','가','요','답변','...','을','수','에','질문','제','를','이','도',
                      '좋','1','는','로','으로','2','것','은','다',',','니다','대','들',
              '이다','하고','입니다','대한','에서','수사','심의',
                      '2017','들','데','..','의','때','겠','고','게','네요','한','일','할',
                      '10','?','하는','06','주','려고','인데','거','좀','는데','~','ㅎㅎ',
                      '하나','이상','20','뭐','까','있는','잘','습니다','다면','했','주려',
                      '지','있','못','후','중','줄','6','과','어떤','기본','!!',
                      '단어','라고','중요한','합','가요','....','보이','네','무지',
             '적', '성', '삼', '등', '전', '인', '그', '했다', '와', '위', '해', '권', '된', '서', '말', '분']

morph_list = [each_word for each_word in morph_list
             if each_word not in stop_words]

ko = nltk.Text(morph_list)
ko.vocab().most_common(5)

[('트리', 16), ('콜', 16), ('커피', 8), ('타케시', 8), ('후기', 7)]라는 결과가 나왔습니다. 이를 좀 더 보기 편하게 시각화 작업을 하겠습니다.

plt.figure(figsize=(15,6))
plt.rc('font', size=10 )
plt.title("만우절 키워드")
plt.xticks(rotation = -45 )
ko.plot(5) 
plt.show()

앞의 결과는 유동(ㅇㅇ)을 제외한 결과입니다. 유동 결과를 포함하면 키워드가 달라질까요? 한 번 확인해보겠습니다.

우선, 유동을 처리하기 전 변수를 가지고와 제목 부분 리스트화, 형태소 분리, 불용어 처리를 거친 후, 가장 많이 나오는 단어 5개를 나타냈습니다.

#리스트로 변환
df_sample_06 = df_sample_01.astype({'제목': str})
list_df_sample_06 = df_sample_06['제목'].values.tolist()

morph_list_2 = []
for i in list_df_sample_06:
    morphs = t.morphs(i)
    for morph in morphs:
        morph_list_2.append(morph)

morph_list_2 = [each_word for each_word in morph_list_2
             if each_word not in stop_words]

ko = nltk.Text(morph_list_2)
ko.vocab().most_common(5)

[('트리', 39), ('콜', 39), ('커피', 29), ('타케시', 16), ('후기', 16)]

다시 시각화를 해보면

#만우절 하루 동안 있었던 일은 무엇일까? (가장 많이 나온 제목 키워드)
plt.figure(figsize=(15,6))
plt.rc('font', size=10 )
plt.title("만우절 키워드")
plt.xticks(rotation = -45 )
ko.plot(5) 
plt.show()

유동을 제외한 것과 같은 양상의 키워드가 나오는 것을 확인하실 수 있습니다. 

이를 통해 만우절 하루 동안, 어그로를 끈 글은 원래 네임드였던 커잘알의 그럴듯한 제목의 글이 차지했습니다. ("부모님이랑 로스터리 열기로 했다.") 그리고 뒤이어 뭔가 불행한 일을 내포한 글이 차지한 것을 볼 수 있었습니다. 

요약하자면, 네임드의 글과 불행한 글이 가장 환호를 많이 받은 글이라는 것을 알 수 있었습니다.

그에 반해 갤러리에서 가장 많이 나온 단어는 트리+콜 (트리콜 ; 트리콜레이트 라는 드리퍼)와 커피, 타케시, 후기와 같은 키워드의 글이 많이 작성되었습니다. 

만우절이지만, 그에 상관없이, 꾸준히 커피에 대한 관심을 내뿜는 갤러리였습니다.

 

+
분석을 하며 도메인 널리지가 분석에 중요하다는 것을 알게 되었습니다. 앞의 그래프 트리, 콜 을 보고는 커피에 관심 없는 일반적인 사람이 볼 때, 이게 뭔지 할 것입니다. 하지만, 저는 평소 커피에 관심을 가지고 있었기 때문에 바로 '아~~ 트리콜 레이트 드리퍼를 말하는 거구나'할 수 있었습니다. 

전체 코드는 제 깃허브에 남겨두겠습니다!

github.com/minseoblim/DC-inside-crawler-Text-mining

 

minseoblim/DC-inside-crawler-Text-mining

Contribute to minseoblim/DC-inside-crawler-Text-mining development by creating an account on GitHub.

github.com


오늘도 읽어주셔서 감사합니다. 뒷모습의 아름다움을 담는 디지비션 =)

 

사진 계정 : @digibition (Instagram)
커피 계정 : @supfo.od (Instagram)
유튜브 : https://www.youtube.com/user/lms9301/videos

 

minseob Lim

커피를 좋아합니다. 특히 다양한 걸 조금씩 먹는걸...

www.youtube.com

댓글