본문 바로가기
IT/Python

파이썬 크롤링-04. 워드클라우드 프로젝트

by 무녈 2021. 6. 26.

자료의 출처는 엘리스 AI 트랙(https://aitrack.elice.io/) '파이썬 크롤링'  이며, 학습 후 정리한 내용입니다.

⚡️올바르지 않은 내용이 있을 경우 댓글로 남겨주시면 감사하겠습니다.⚡️


워드클라우드 프로젝트

워드클라우드

워드클라우드란?

데이터에서 단어 빈도를 분석하여 시각화하는 기법

워드클라우드 준비

워드클라우드를 그리기 위해서 텍스트 데이터가 필요

네이버 뉴스 기사의 내용의 텍스트 데이터로

워드클라우드를 그려보도록 하겠다.

영어 문장 나누기

워드클라우드의 각 단어는 빈도에 따라 크기가 결정

크기가 큰 단어일수록 빈도가 높다.

영어 문장의 경우, 공백을 기준으로 나누어 각각의 단어를 얻을 수 있다.

 

영어로 이루어진 텍스트 데이터가 주어진다.

텍스트 데이터를 공백을 기준으로 나누어 빈도를 조사하고,

이를 바탕으로 워드클라우드를 그려보도록 하겠자.

 

# 실습 - 영어 문장 나누기

워드 클라우드는 텍스트 데이터에서 단어가 등장한 횟수를 기준으로 표현하는 그림이므로, 주어진 텍스트를 단어 단위로 나누어야 합니다.

text.py 파일에 준비된 문자열이 있습니다. 주어진 문자열을 공백 문자를 기준으로 나누고, 각 단어별 횟수를 센 결과를 반환하는  

count_word_freq 함수를 구현해보세요.

count_word_freq 함수는 문자열을 입력받고, 문자열 내의 단어들이 몇 번 등장하는지 센 결과를 반환합니다.

 

지시사항

1. 필요한 모듈

from collections import Counter
from string import punctuation

count_word_freq 함수를 구현하기 위해서, collections와 string 모듈의 Counter, punctuation을 불러와야 합니다.

Counter는 주어진 리스트에서 특정 값이 몇 번 등장하는지 세는 역할을 하고,

pucntuation은 특수문자들이 담겨있는 문자열입니다. punctuation으로 문자열 데이터에서 특수문자를 제거하려고 합니다.

2. 전처리

_data = data.lower()

문자열들을 모두 소문자로 바꿉니다.

for p in punctuation :
    _data = _data.replace(p, "")

문자열에 들어있는 특수문자를 모두 제거합니다. punctuation은 특수문자들이 담겨 있는 문자열 변수입니다.

3. 단어 나누기

_data = _data.split()

split 함수를 이용하여 공백 문자를 기준으로 문자열을 나눕니다.

4. 단어 세기

counter = Counter(_data)

문자열을 나눈 리스트를 이용하여 Counter 객체를 만들어줍니다.

return counter

단어를 센 결과를 반환합니다.

from collections import Counter
from string import punctuation
from text import data 

def count_word_freq(data) :
    _data = data.lower()
    
    for p in punctuation :
        _data = _data.replace(p, "")
    
    _data = _data.split()
    
    counter = Counter(_data)
    return counter

if __name__ == "__main__" :
    count_word_freq(data)

# 실습 - 워드클라우드 출력하기

아래 지시사항에 적힌 예시 코드를 참고하여 워드클라우드를 출력하는 함수 create_word_cloud를 구현해보세요.

create_word_cloud 함수는 문자열 데이터를 입력받고, 해당 문자열의 워드클라우드를 출력합니다.

지시사항

from wordcloud import WordCloud

워드클라우드를 그리기 위해서 WordCloud모듈이 필요합니다.

WordCloud의 fit_words라는 함수가 단어들의 빈도 수가 담긴 딕셔너리를 매개변수로 받아, 워드클라우드를 그리는 역할을 합니다.

1. 단어 빈도수 얻기

from wordcloud import WordCloud

문자열 data에 들어있는 단어들의 빈도수를 얻어 count에 저장합니다.

2. 워드클라우드 객체 생성하기

cloud = WordCloud(background_color='white')

배경색이 흰색인 WordCloud 객체를 생성합니다.

3. 워드클라우드 그리기

cloud.fit_words(counter)

단어들의 횟수를 기반으로 워드클라우드를 생성합니다.

cloud.to_file('cloud.png')
elice_utils.send_image('cloud.png')

생성한 워드클라우드를 그림 파일로 저장하고, 엘리스 플랫폼에서 출력합니다.

from wordcloud import WordCloud
from count import count_word_freq
from text import data
from elice_utils import EliceUtils
elice_utils = EliceUtils()

def create_word_cloud(data) :
    counter = count_word_freq(data)
    #코드를 작성하세요.
    cloud = WordCloud(background_color='white')
    
    cloud.fit_words(counter)
    
    cloud.to_file('cloud.png')
    elice_utils.send_image('cloud.png')
    
    
    
if __name__ == "__main__" :
    create_word_cloud(data)

네이버 뉴스 기사 워드클라우드

네이버 뉴스 기사 워드클라우드

본격적으로 네이버 뉴스 기사의 워드클라우드를 그려보도록 하겠습니다.

이전 장의 실습에서 활용했던 코드로 네이버 뉴스 기사의 내용을 크롤링하세요.

 

원하는 기사의 URL을 입력하시고,

워드클라우드를 출력해보신 후 출력된 모습을 관찰해보세요.

이전 실습의 문제점

이전 실습에서 그렸던 워드클라우드의 문제점은

단어에 어미조사가 붙어 분석이 왜곡되는 것입니다.

 

예를 들어 ‘대통령이’ 와 ‘대통령은’은 둘 다

대통령이라는 공통된 키워드로 집계되어야 합니다.

형태소 추출

이를 추출하기 위해 한국어 단어에 붙는
어미와 조사를 제거하고
, 단어의 어근만 집계되도록 하는

형태소 추출 과정이 필요합니다.

# 실습 - 네이버 뉴스 기사 내용 크롤링하기

네이버 뉴스 어떤 기사의 내용을 추출하고, 하나의 문자열로 저장해보세요.

네이버 뉴스의 어떤 기사라도 좋습니다.

해당 기사의 내용을 크롤링하는 코드를 crawling 함수에 작성해보세요.

2장 다양한 섹션의 속보 기사 내용 추출하기 실습에서 작성하셨던 코드를 이용하셔도 됩니다.

import requests
from bs4 import BeautifulSoup

def crawling(soup) :
    # soup 객체에서 추출해야 하는 정보를 찾고 반환하세요.
    div = soup.find("div", class_="_article_body_contents")
    
    result = div.get_text().replace("\n", "").replace('// flash 오류를 우회하기 위한 함수 추가function _flash_removeCallback() {}', '').replace('\t', '')
    
    return result    
    
def main() :
    custom_header = {'user-agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'}
    url = "https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=001&aid=0011575988"
    req = requests.get(url, headers=custom_header)
    soup = BeautifulSoup(req.text, "html.parser")
    
    print(crawling(soup))
    


if __name__ == "__main__" :
    main()

# 실습 - 네이버 뉴스 기사 워드클라우드 출력하기

앞에서 추출한 네이버 뉴스 기사의 내용에서 워드클라우드를 출력해보도록 하겠습니다.

main함수의 url 변수에 분석하길 원하는 네이버 뉴스 기사 주소를 넣어 분석할 수 있습니다.

# main.py
import requests
from bs4 import BeautifulSoup
from wc import create_word_cloud

def crawling(soup) :
    # 기사에서 내용을 추출하고 반환하세요.
    div = soup.find('div', class_="_article_body_contents")
    
    result = div.get_text().replace('\n', '').replace('// flash 오류를 우회하기 위한 함수 추가function _flash_removeCallback() {}', '').replace('\t', '')
    
    return result
    
    
def main() :
    custom_header = {'user-agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'}
    url = "https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=001&aid=0011575988"
    req = requests.get(url, headers=custom_header)
    soup = BeautifulSoup(req.text, "html.parser")
    
    text = crawling(soup)
    create_word_cloud(text)
    

if __name__ == "__main__" :
    main()
# count.py
from collections import Counter
from string import punctuation

def count_word_freq(data) :
    _data = data.lower()
    
    for p in punctuation :
        _data = _data.replace(p, "")
    
    _data = _data.split()
    
    counter = Counter(_data)
    
    return counter
# wc.py
from wordcloud import WordCloud
from count import count_word_freq
from elice_utils import EliceUtils
elice_utils = EliceUtils()

def create_word_cloud(data) :
    counter = count_word_freq(data)
    
    cloud = WordCloud(font_path='NanumBarunGothic.ttf' ,background_color='white')
    cloud.fit_words(counter)
    cloud.to_file('cloud.png')
    elice_utils.send_image('cloud.png')
    
if __name__ == "__main__" :
    create_word_cloud(data)

형태소 추출

이 과정에서 한국어 자연어 처리 라이브러리인 mecab을 사용합니다.

# 실습 - 형태소 추출

지시사항

mecab = MeCab() 으로 mecab 객체를 생성할 수 있습니다.

mecab.morphs(text) 함수는, 매개변수로 들어온 문장을 형태소별로 나누어 리스트로 반환합니다.

mecab.nouns(text) 함수는, 매개변수로 들어온 문장을 형태소별로 나누었을 때 명사만 추출하여 리스트로 반환합니다.

mecab.pos(text) 함수는, 매개변수로 들어온 문장을 형태소별로 나누고, 각 형태소별로 품사에 대한 정보까지 포함하여 반환합니다.

주어진 text 변수, 또는 자유롭게 text 변수의 값을 설정하여 mecab 모듈의 함수를 사용해보세요.

from mecab import MeCab
mecab = MeCab()

text = "광화문역 주변에 있는 맛집을 알고 싶어요. 정보를 얻을 수 있을까요?"

print(mecab.morphs(text))# 1. 형태소 별로 나눠 출력해보기


print(mecab.nouns(text))# 2. 명사만 출력해보기


print(mecab.pos(text))# 3. 형태소 별로 나누고 품사 출력해보기

# 실습- 형태소 추출하기

이번 워드클라우드는 주어진 텍스트 데이터에서 명사만 추출한 것으로 그려보도록 하겠습니다.

count.py 파일의 count_word_freq 함수에 명사를 추출하는 코드를 추가해주셔야 합니다.

main.py파일의 main 함수에서 사용한 url :
https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=101&oid=031&aid=0000556282

# count.py
from collections import Counter
from string import punctuation
import mecab
mecab = mecab.MeCab()

def count_word_freq(data) :
    _data = data.lower()
    
    for p in punctuation :
        _data = _data.replace(p, "")
    
    # 명사를 추출하세요.
    
    
    counter = Counter(_data)
    
    return counter
#main.py
import requests
from bs4 import BeautifulSoup
from wc import create_word_cloud

def crawling(soup) :
    # 기사에서 내용을 추출하고 반환하세요.
    div = soup.find('div', class_="_article_body_contents")
    
    result = div.get_text().replace('\n', '').replace('// flash 오류를 우회하기 위한 함수 추가function _flash_removeCallback() {}', '').replace('\t', '')
    
    return result
    
    
def main() :
    custom_header = {'user-agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'}
    url = "https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=101&oid=031&aid=0000556282"
    req = requests.get(url, headers=custom_header)
    soup = BeautifulSoup(req.text, "html.parser")
    
    text = crawling(soup)
    create_word_cloud(text)
    

if __name__ == "__main__" :
    main()

여러 개의 기사 내용 크롤링하기

하나의 기사만으로는 단어의 빈도수를 파악하기 어려울 수 있습니다.

기사의 분량, 기자의 성향 등 여러 요인이 반영되기 때문입니다.

 

따라서 공통된 주제에 대한 여러 기사의 텍스트 데이터를 같이 분석하면 효

과적인 워드클라우드를 출력할 수 있습니다.

 

네이버 뉴스 페이지는 관련된 주제의 여러 기사를 묶어서 보여주고 있습니다.

 

각각의 분야(정치, 경제, 사회, 생활, 세계, 과학)에 대해

페이지 최상단에 보이는 주제에 해당하는 기사들의

텍스트 데이터로 워드클라우드를 출력해봅시다.

# 실습 - 여러 개의 기사 내용 크롤링하기

이번에는 여러 기사들의 텍스트 데이터를 모아와서 워드클라우드를 출력해보겠습니다.

뉴스 게시판은 “정치”, “경제”, “사회”, “생활”, “세계”, “과학” 분야로 나눠져 있습니다.

분야를 입력하면(정치, 경제 등) 해당 분야에 해당하는 여러 기사들의 내용을 크롤링하려고 합니다.

아래 지시사항의 내용을 읽고 코드를 작성해보세요.

import requests
from bs4 import BeautifulSoup

def crawling(soup) :
    # 기사에서 내용을 추출하고 반환하세요.
    div = soup.find('div', class_="_article_body_contents")
    
    result = div.get_text().replace('\n', '').replace('// flash 오류를 우회하기 위한 함수 추가function _flash_removeCallback() {}', '').replace('\t', '')
    
    return result
    

def get_href(soup) :
    result = []
    
    ul = soup.find("ul", class_="cluster_list")
    
    for div in ul.find_all("div", class_="cluster_text"):
        result.append(div.find("a")["href"])
    
    return result


def get_request(section, custom_header) :
    url = "https://news.naver.com/main/main.nhn"
    section_dict = { "정치" : 100,
                     "경제" : 101,
                     "사회" : 102,
                     "생활" : 103,
                     "세계" : 104,
                     "과학" : 105 }
    return requests.get(url, params={"sid1":section_dict[section]}, headers=custom_header)


def main() :
    custom_header = {'user-agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'}
    list_href = []
    result = []
    
    # 섹션을 입력하세요.
    section = input('"정치", "경제", "사회", "생활", "세계", "과학" 중 하나를 입력하세요.\n  > ')
    
    req = get_request(section, custom_header)
    soup = BeautifulSoup(req.text, "html.parser")
    
    list_href = get_href(soup)
    
    for href in list_href :
        href_req = requests.get(href, headers=custom_header)
        href_soup = BeautifulSoup(href_req.text, "html.parser")
        result.append(crawling(href_soup))
    print(result)


if __name__ == "__main__" :
    main()

# 실습 - 여러 개의 기사 내용으로 워드클라우드기

# main.py
import requests
from bs4 import BeautifulSoup
from wc import create_word_cloud


def crawling(soup) :
    # 기사에서 내용을 추출하고 반환하세요.
    div = soup.find('div', class_="_article_body_contents")
    
    result = div.get_text().replace('\n', '').replace('// flash 오류를 우회하기 위한 함수 추가function _flash_removeCallback() {}', '').replace('\t', '')
    
    return result
    

def get_href(soup) :
    result = []
    
    cluster_body = soup.find("div", class_ = "cluster_body")
    
    for cluster_text in cluster_body.find_all("div", class_ = "cluster_text") :
        result.append(cluster_text.find("a")["href"])
    
    return result


def get_request(section, custom_header) :
    url = "https://news.naver.com/main/main.nhn"
    section_dict = { "정치" : 100,
                     "경제" : 101,
                     "사회" : 102,
                     "생활" : 103,
                     "세계" : 104,
                     "과학" : 105 }
    return requests.get(url, params={"sid1":section_dict[section]}, headers=custom_header)


def main() :
    custom_header = {'user-agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'}
    list_href = []
    result = []
    
    # 섹션을 입력하세요.
    section = input('"정치", "경제", "사회", "생활", "세계", "과학" 중 하나를 입력하세요.\n  > ')
    
    req = get_request(section, custom_header)
    soup = BeautifulSoup(req.text, "html.parser")
    
    list_href = get_href(soup)
    
    for href in list_href :
        href_req = requests.get(href, headers=custom_header)
        href_soup = BeautifulSoup(href_req.text, "html.parser")
        result.append(crawling(href_soup))
    
    text = " ".join(result)
    create_word_cloud(text)
    


if __name__ == "__main__" :
    main()
#count.py
from collections import Counter
from string import punctuation
import mecab
mecab = mecab.MeCab()

def count_word_freq(data) :
    _data = data.lower()
    
    for p in punctuation :
        _data = _data.replace(p, "")
    
    # 명사 추출
    _data = mecab.nouns(_data)
    
    counter = Counter(_data)
    
    return counter
#wc.py
from wordcloud import WordCloud
from count import count_word_freq
from elice_utils import EliceUtils
elice_utils = EliceUtils()

def create_word_cloud(data) :
    counter = count_word_freq(data)
    
    cloud = WordCloud(font_path='NanumBarunGothic.ttf' ,background_color='white')
    cloud.fit_words(counter)
    cloud.to_file('cloud.png')
    elice_utils.send_image('cloud.png')
    
if __name__ == "__main__" :
    create_word_cloud(data)

더 많은 기사 내용 크롤링하기

이전 실습으로 각 주제마다 3~4개 기사

텍스트 데이터를 크롤링 할 수 있게 되었습니다.

이 상태에서, 더 많은 기사의 내용을 크롤링하면

텍스트 데이터를 풍부하게 만들 수 있습니다.

기사 페이지에서 더 많은 기사를 확인할 수 있습니다.

세부 페이지에서 더 많은 기사에 각각 접근하실 수 있습니다.

# 실습 - 더 많은 기사로 워드클라우드 출력하기

# main.py
import requests
from bs4 import BeautifulSoup
from wc import create_word_cloud

def crawling(soup) :
    # 기사에서 내용을 추출하고 반환하세요.
    div = soup.find('div', class_="_article_body_contents")
    
    result = div.get_text().replace('\n', '').replace('// flash 오류를 우회하기 위한 함수 추가function _flash_removeCallback() {}', '').replace('\t', '')
    
    return result
    
    
def get_href(soup, custom_header) :
    result = []
    
    cluster_head = soup.find("h2", class_="cluster_head_topic")
    href = cluster_head.find("a")["href"]
    
    url = "https://news.naver.com" + href
    req = requests.get(url, headers=custom_header)
    new_soup = BeautifulSoup(req.text, "html.parser")
    
    main_content = new_soup.find("div", id="main_content")
    
    for ul in main_content.find_all("ul") :
        for a in ul.find_all("a") :
            result.append(a["href"])
    
    return result


def get_request(section, custom_header) :
    url = "https://news.naver.com/main/main.nhn"
    section_dict = { "정치" : 100,
                     "경제" : 101,
                     "사회" : 102,
                     "생활" : 103,
                     "세계" : 104,
                     "과학" : 105 }
    return requests.get(url, params={"sid1":section_dict[section]}, headers=custom_header)

# 추가된 코드입니다.
def get_href_politics(soup, custom_header) :
    result = []
    
    cluster_head = soup.find("div", class_="cluster_foot_inner")
    href = cluster_head.find("a")["href"]
    
    url = "https://news.naver.com" + href
    req = requests.get(url, headers=custom_header)
    new_soup = BeautifulSoup(req.text, "html.parser")
    
    main_content = new_soup.find("div", id="main_content")
    
    for ul in main_content.find_all("ul") :
        for a in ul.find_all("a") :
            result.append(a["href"])
    
    return result
    
    
def main() :
    custom_header = {'user-agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'}
    list_href = []
    result = []
    
    # 섹션을 입력하세요.
    section = input('"정치", "경제", "사회", "생활", "세계", "과학" 중 하나를 입력하세요.\n  > ')
    
    req = get_request(section, custom_header)
    soup = BeautifulSoup(req.text, "html.parser")
    
    # 추가된 코드입니다.
    if section != "정치":
        list_href = get_href(soup, custom_header)
    else:
        list_href = get_href_politics(soup, custom_header)
    
    for href in list_href :
        href_req = requests.get(href, headers=custom_header)
        href_soup = BeautifulSoup(href_req.text, "html.parser")
        result.append(crawling(href_soup))
    
    text = " ".join(result)
    create_word_cloud(text)
    

if __name__ == "__main__" :
    main()
# count.py
from collections import Counter
from string import punctuation
import mecab
mecab = mecab.MeCab()

def count_word_freq(data) :
    _data = data.lower()
    
    for p in punctuation :
        _data = _data.replace(p, "")
    
    # 명사 추출
    _data = mecab.nouns(_data)
    
    counter = Counter(_data)
    
    return counter
# wc.py
from wordcloud import WordCloud
from count import count_word_freq
from elice_utils import EliceUtils
elice_utils = EliceUtils()

def create_word_cloud(data) :
    counter = count_word_freq(data)
    
    cloud = WordCloud(font_path='NanumBarunGothic.ttf' ,background_color='white')
    cloud.fit_words(counter)
    cloud.to_file('cloud.png')
    elice_utils.send_image('cloud.png')
    
if __name__ == "__main__" :
    create_word_cloud(data)
반응형

댓글