본문 바로가기

DB엔지니어가 공부하는 python

[python] 파이썬으로 네이버 카페 게시판 크롤링 & 워드 클라우드 실습 하기! (feat.konlpy.Twitter)

안녕하세요.

 

데이터 분석의 첫걸음으로 워드 클라우드를 분석하기 위해

 

제가 가입해 활동하고 있는 자동차 네이버 카페의 자유게시판을 크롤링했습니다.

 

지난 2019년 작성된 자동차 카페 내 자유게시판의 게시글과 본문을 크롤링했습니다.

 

아직 실력이 모자라, 한글로 된 데이터만 워드 클라우드에 넣을 수 있었습니다.

 

한 번에 할 수 없는 실력이라 ㅎㅎ

 

먼저 크롤링을 하는 파이썬 코드를 짜고 크롤링한 데이터를 DB에 insert 하는 부분과

 

DB에 쌓인 내용을 text 파일로 묶어 konlpy 라이브러리를 이용하여 명사만 추출하여 카운트하고

 

골라낸 데이터를 워드 클라우드로 viewing 하는 부분으로 나누어 진행했습니다.

 

고수님들의 거침없는 질책 부탁드립니다!!

 

 

<part1>

자동차 네이버 카페에 있는 자유게시판 게시글 제목과 게시글 본문을 크롤링한다.

 

import time
import pandas as pd
import os
import pymysql
from selenium import webdriver # pip install selenium
from bs4 import BeautifulSoup as bs # pip install bs4

필요한 라이브러리를 import 합니다.

# chrome 드라이버
driver = webdriver.Chrome('/Users/whiki/Downloads/chromedriver_win32/chromedriver.exe')

chrome에 접속해야 하기에.. chrome 드라이버를 가져온다.

 

driver.get('https://nid.naver.com/nidlogin.login?svctype=262144&url=http://m.naver.com/')

# id & pw 입력
driver.find_element_by_name('id').send_keys('id')  #네이버 아이디
driver.find_element_by_name('pw').send_keys('password')  #네이버 패스워드

# click login
driver.find_element_by_css_selector('#frmNIDLogin > fieldset > input').click()

네이버 아이와 pw를 입력해준다.

 

# 카페로 이동
driver.get('https://cafe.naver.com/xxxxxxxxxxxxxxxxxxxxxxxxxx')

크롤링을 원하는 카페 주소를 입력한다.

 

이까지 입력하고 실행하시면 카페가 chrome 브라우저로 열리는 것을 확인할 수 있습니다.

 

# base_url = cafe main page url
base_url = 'https://cafe.naver.com/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
 
cnt = 0  # number of collected data
page = 0 # position of current page

# db connect and select
conn = pymysql.connect(host='192.168.1.25',
                      user = 'db_user', password='db_pw', db = 'schema_name',charset = 'utf8')

curs = conn.cursor(pymysql.cursors.DictCursor)

job_seq = 0

 

db 유저명과 pw, 그리고 접속하는 schema 이름을 입력합니다.

 

전 mariadb와 연결했습니다.

 

while page < 102 : # 게시글 페이지 수 입니다. 올해글이 약 102page를 차지하고 있었습니다.
     page = page + 1
     quest_urls = []
     try :
     	  # add personal conditions
          	# &search.menuid = : 게시판 번호(카페마다 상이)
          	# &search.page = : 데이터 수집 할 페이지 번호
          	# &userDisplay = 50 : 한 페이지에 보여질 게시글 수
          driver.get(base_url + '&search.menuid=2&search.page='+ str(page) +'&userDisplay=50')
          driver.switch_to.frame('cafe_main') #iframe으로 프레임 전환
          quest_list = driver.find_elements_by_css_selector('div.inner_list > a.article') 
          quest_urls = [ i.get_attribute('href') for i in quest_list ]
          print(len(quest_urls))

          for quest in quest_urls :
               try : #게시글이 삭제되었을 경우가 있기 때문에 try-exception
                    driver.get(quest)
                    driver.switch_to.frame('cafe_main')
                    soup = bs(driver.page_source, 'html.parser')         
                    #제목 추출
                    title = soup.select('div.tit-box span.b')[0].get_text()
                    print(title)

                    #내용 추출
                    content_tags = soup.select('#tbody')[0].select('p')
                    content = ' '.join([ tags.get_text() for tags in content_tags ])
                    
                    print(content)
                    
                    job_seq = job_seq+1

                    sqlInsert = "INSERT INTO schema_name.table_name VALUES (%s,%s,%s)"
                    val = (job_seq,title,content)
                    curs.execute(sqlInsert,val)
                    
                    conn.commit()
                    
                    
                    #말머리 추출
                    try :
                        tag = soup.select('div.tit-box span.head')[0].get_text()
                        temp_list = [title, content]
                        f = open('preg_quest.csv', 'a+', encoding = 'ansi', newline='')
                        wr = csv.writer(f)
                        wr.writerow(temp_list)
                        f.close()
                        cnt = cnt + 1
                    except : # 말머리 없으면 next
                         pass
               except : # chrome alert창 처리해줌
                    driver.switch_to_alert.accpet()
                    driver.switch_to_alert
                    driver.switch_to_alert.accpet()
     except :
          pass
     print([page, cnt]) #page로는 진행상황을 알 수 있고 cnt로는 몇개의 데이터를 모았는지 알 수 있음

conn.close()

위 소스는 이젠 크롤링을 하는 소스인데,

 

저 같은 경우는 검색을 통해서 찾았습니다.

 

사실 아직 저 소스들을 제 것으로 만들지는 못했습니다만, 여러 번의 error를 수정하면서

 

크롤링이 실제로 되는 것을 경험했습니다.

 

조금 더 공부하겠습니다!

(지금 보닌 깐 원문 소스를 올려주신 블로그를 못 찾겠어서... 출처를 넣을 수가 없습니다...ㅠㅠ 혹시라도 보시면 댓글 부탁드립니다.)

 

자, 이렇게 게시글 제목과 본문을 크롤링했습니다.

 

DB에 잘 쌓이는지 확인했고요. 

 

뭐, 고수들은 여기서 바로 이어서 워드 클라우드까지 가겠지만, 전 그 방법을 아직 못 찾아서..

 

DB에 쌓인 데이터를 게시글 제목과, 본문으로 각각 merge 하여 text로 전환하는 처리는 DB에서 했습니다.

 

여담이지만, 생각보다 오래 걸립니다.. 그냥 예상은 쫙~~ 되고 끝날 줄 알았는데.. 하나하나 창을 열어서

 

크롤링이 진행이 되더라고요. 혹시 다른 빠른 방법을 아시면 댓글로 알려주시면 감사하겠습니다.

 

그렇게 text 파일을 게시글 제목, 게시글 본문 두 개로 만들어서 다음 part로 넘어가겠습니다.

 

<part2>

이젠 앞서 만든 text 파일 두 개를 가지고 konlpy 라이브러리를 통해 단어를 쪼개고 분석하여 명사로만

 

워드 클라우드를 만들어 보겠습니다.

from konlpy.tag import Twitter 
from collections import Counter

마찬가지로 필요한 라이브러리를 import 합니다.

 

file = open("E:/text_analyze/title.txt", "r", encoding='UTF8') 
lists = file.readlines() 
file.close() 

파일을 열어야겠죠?

 

게시글 제목을 먼저 열었습니다. encoding을 지정한 이유는 한글이 깨져 보여서입니다.

twitter = Twitter() 
morphs = [] 

for sentence in lists: 
    morphs.append(twitter.pos(sentence)) 
    
print(morphs)

읽은 파일을 konlpy Twitter를 통해서 단어 분석을 합니다.

 

이건 명사고, 동사고, 특수문자이고, 외국어이고.. 뭐 그렇게 하나하나 분리해서 분석합니다.

noun_adj_adv_list=[] 

for sentence in morphs : 
    for word, tag in sentence : 
        if tag in ['Noun'] and ("것" not in word) and ("저" not in word) and ("등" not in word) and ("전" not in word) and ("요" not in word) and ("분" not in word) and ("시" not in word) and ("카" not in word) and ("너" not in word) and ("및" not in word) and ("이" not in word) and ("거" not in word) and ("좀" not in word) and ("제" not in word) and ("후" not in word) and ("비" not in word) and ("내" not in word)and ("나" not in word)and ("수"not in word) and("게"not in word)and("말"not in word): 
            noun_adj_adv_list.append(word) 
            
print(noun_adj_adv_list)

다음은 거기서 명사만 추출을 합니다.

 

그중에서 필요 없는 tag들은 빼버립니다. 중간에 보이는 if 문에서 필요 없는 단어를 제외시켜 줍니다.

 

보통은 한 글자짜리는 빼버리는데, 한글자 명사도 필요한 것이 있어서 전 if 문으로 하나하나 걸러 주었습니다.

count = Counter(noun_adj_adv_list)

words = dict(count.most_common())

words

그런 다음, 명사 별로 빈도수를 카운트해서 출력해 봅니다.

 

워드 클라우드의 바로 전 단계입니다.

이젠 워드 클라우드를 할 차례가 왔습니다.

from wordcloud import WordCloud 
import matplotlib.pyplot as plt 

import nltk 
from nltk.corpus import stopwords

워드 클라우드에 필요한 라이브러리를 import 합니다.

 

%matplotlib inline 

import matplotlib 
from IPython.display import set_matplotlib_formats 
matplotlib.rc('font',family = 'Malgun Gothic') 

set_matplotlib_formats('retina') 

matplotlib.rc('axes',unicode_minus = False)

글꼴 지정을 위해서 위 라이브러리들도 import 하는 것으로 알고 있습니다.

 

혹시나 제가 실수한 거라면... 댓글로 지적 바랍니다!

import numpy as np
import random
from PIL import Image

r4_mask = np.array(Image.open("E:/python_source/mu_mask.png"))

그리고 마스킹을 위해서 라이브러리를 import 하고, 마스킹 이미지도 지정해 줍니다.

 

주의할 점은 마스킹 이미지는 꼭 하얀 바탕에 검은 그림이어야 잘 된다는 겁니다.

wordcloud = WordCloud(background_color="black", 
                      font_path = 'C:/Windows/Fonts/NanumBrush.ttf', 
                      colormap = "prism",
                      width = 800,
                      height = 800
                      #,mask = r4_mask
)


wordcloud = wordcloud.generate_from_frequencies(words)
plt.figure(figsize=(12, 12))
plt.imshow(wordcloud, interpolation="bilinear")
plt.axis("off")
plt.show()

그렇게 마지막으로 워드 클라우드를 실행합니다.

 

글꼴도 지정해주고, colormap이나 크기 등을 지정합니다.

 

소스에선 마스킹은 주석 처리되어 있습니다.

 

여길 풀면 마스킹이 됩니다.

 

결과물은 아래와 같이 마스킹한 것과 안 하고한 일반적인 사각형 모양입니다.

 

허접하지만, 그래도 많이 검색한 결과이니 잘 봐주시기 바랍니다!

 

앞으로 많이 공부하겠습니다!!

 

감사합니다!!