본문 바로가기
빅데이터 부트캠프/Crawling&Scraping

빅데이터 부트캠프 37일차

by Mecodata 2022. 8. 25.

네이버 뉴스 기사 크롤링 (단어 빈도수 분석 후 그래프로 시각화)

 

import sys
import requests
from bs4 import BeautifulSoup
from newspaper import Article # 사용자가 지정한 URL에서 텍스트를 추출
from konlpy.tag import Okt
from collections import Counter
from collections import OrderedDict # 입력된 데이터들의 순서를 기억하는 딕셔너리 클래스

import matplotlib
import matplotlib.pyplot as plt


URL_BEFORE_KEYWORD = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query="
URL_BEFORE_PAGE_NUM = '&sort=0&photo=0&field=0&pd=0&ds=&de=&cluster_rank=1&mynews=0&office_type=' \
                      '0&office_section_code=0&news_office_checked=&nso=so:r,p:all,a:all&start='

font_name = "Malgun Gothic"

def get_link(key_word, page_range):
    link = []

    for page in range(page_range):
        current_page = 1 + page * 10 # 네이버뉴스 URL 1 -> 11 -> 21 -> ...
        crawling_url_list = URL_BEFORE_KEYWORD + key_word + URL_BEFORE_PAGE_NUM + str(current_page)

        # URL에 get 요청 보내기 (>>200 -> 서버에서 잘 처리되어 OK 싸인 전송)
        response = requests.get(crawling_url_list)

        # 응답의 내용을 HTML 코드, 'lxml' 파서를 이용하여 분석
        soup = BeautifulSoup(response.text, 'lxml')

        # HTML 문서에 각 기사의 URL을 갖고 있는 태그 선택 & 저장
        url_tag = soup.select('div.news_area > a')
        # soup.select('상위 태그명 > 하위 태그명)
        # select를 하면 찾은 태그들을 리스트 형태로 반환
        print(url_tag)
        # url_tag에 저장되어 있는 각 기사의 URL을 link 리스트에 저장
        for url in url_tag:
            link.append(url['href']) # href = 연결할 주소를 지정하는 속성

    return link

def get_article(file1, link, key_word, page_range): # 기사를 저장할 파일, 링크, 키워드, 페이지 범위
    print('데이터를 불러오는 중...')

    # 기사 내용을 저장할 파일을 쓰기 모드로 열기
    with open(file1, 'w', encoding='utf8') as f:
        i = 1

        for url2 in link:
            article = Article(url2, language='ko') # url2 안에 있는 텍스트 추출

            try:
                article.download() # 기사 다운로드
                article.parse() # 기사 분석
            except:
                print('-', i, '번째 URL을 크롤링할 수 없습니다.')
                continue

            news_title = article.title
            news_content = article.text

            f.write(news_title)
            f.write(news_content)

            i += 1

    # i = 11이 됐을 때 url2에 포함되지 않기 때문에 반복문을 빠져나옴 -> 따라서 출력은 i - 1
    print('- 네이버 뉴스 ' + key_word + ' 관련 뉴스기사', str(page_range) + ' 페이지(기사' + str(i - 1) + '개)가 저장되었습니다. (crawling.txt)\n')


def wordcount(file1, file2):

    # 단어와 개수를 저장하는 파일 생성
    f = open(file1, 'r', encoding='utf8')
    g = open(file2, 'w', encoding='utf8')

    engine = Okt() # Open Korea Text
    data = f.read()
    all_nouns = engine.nouns(data)
    nouns = [n for n in all_nouns if len(n) > 1] # 1글자는 의미 없으니 삭제

    global count, by_num

    count = Counter(nouns) # 단어 빈도수 세기
    
    # 단어와 단어의 빈도수를 딕셔너리 형태로 변환
    by_num = OrderedDict(sorted(count.items(), key=lambda t: t[1], reverse=True)) # counts.items의 값을 기준으로 내림차순 정렬
    word = [i for i in by_num.keys()]
    number = [i for i in by_num.values()]

    for w, n in zip(word, number):
        final1 = f"{w}  {n}"
        g.write(final1 + '\n')
    print('- 단어 카운팅이 완료되었습니다. (wordcount.txt)\n')

    f.close(), g.close()

def full_vis_bar(by_num):
    print('그래프를 생성하는 중...')

    for w, n in list(by_num.items()):
        if n <= 15: # 단어의 빈도수 15번 이하 삭제
            del by_num[w]

    fig = plt.gcf() # gcf= get reference to the current figure // 현재 시각화되는 객체에 접근할 필요성이 생길 때
    fig.set_size_inches(20, 10) # 그래프 사이즈
    matplotlib.rc('font', family=font_name, size=10) # 그래프 폰트 초깃값 설정
    plt.title('기사에 나온 전체 단어 빈도 수', fontsize=30) # 그래프 제목
    plt.xlabel('기사에 나온 단어', fontsize=20) # 그래프 X축 label
    plt.ylabel('기사에 나온 단어의 개수', fontsize=20) # 그래프 Y축 label
    plt.bar(by_num.keys(), by_num.values(), color='green') # X축, Y축, 추가 특징 기입
    plt.xticks(rotation=45) # X축 값들 45도로 기울이기
    plt.savefig('all_words.jpg') # 그래프 이미지 저장
    plt.show() # 그래프 출력
    print(' - all_words.jpg가 저장되었습니다.\n')

def top_n(file3):
    print('가장 많이 나온 단어 10개 추출 중...')

    f = open(file3, 'w', encoding='utf8')

    rank = count.most_common(10)

    global top

    top = dict(rank)
    word = [i for i in top.keys()]
    number = [i for i in top.values()]

    for w, n in zip(word, number):
        final2 = f'{w}  {n}'
        f.write(final2 + '\n')

    print('- 최다 빈출 단어 10개가 저장되었습니다. (top.txt)\n')

    f.close()

def topn_vis_bar(top):
    print('그래프를 생성하는 중...')

    fig = plt.gcf()
    fig.set_size_inches(15, 10)
    matplotlib.rc('font', family=font_name, size=20)
    plt.title('기사에 많이 나온 단어 Top 10', fontsize=35)
    plt.xlabel('기사에 많이 나온 단어', fontsize=30)
    plt.ylabel('기사에 많이 나온 단어의 개수', fontsize=30)
    plt.bar(top.keys(),  top.values(), color='purple')
    plt.savefig('top_words.jpg')
    plt.show()
    print('- top_words.jpg가 저장되었습니다.\n')

# def main(argv):
#
#     if len(argv) != 3:
#         print('사용법을 모르시는군요.')
#         print('사용법 : python [모듈이름] [키워드] [가져올 페이지 숫자]')
#         return

file1 = 'crawling.txt'
file2 = 'wordcount.txt'
file3 = 'top.txt'

key_word = input('키워드를 입력하시오 : ')
page_range = int(input('페이지 범위를 입력하시오 : '))

link = get_link(key_word, page_range)
get_article(file1, link, key_word, page_range)
wordcount(file1, file2)
full_vis_bar(by_num)
top_n(file3)
topn_vis_bar(top)

# if __name__ == '__main__': # 이걸 입력해야 터미널에 여러 변수 입력 가능
#     main(sys.argv)

키워드 - 피자
키워드 - 피자

워드클라우드

 

import matplotlib.pyplot as plt
import numpy as np
import requests
from bs4 import BeautifulSoup
from newspaper import Article
from konlpy.tag import Okt
from collections import Counter
from wordcloud import WordCloud
from PIL import Image # PIL(Python Image Library) = 파이썬 이미지 처리 라이브러리

URL_BEFORE_KEYWORD = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query="
URL_BEFORE_PAGE_NUM = '&sort=0&photo=0&field=0&pd=0&ds=&de=&cluster_rank=1&mynews=0&office_type=' \
                      '0&office_section_code=0&news_office_checked=&nso=so:r,p:all,a:all&start='

font_name = "Malgun Gothic"

def get_link(key_word, page_range):
    link = []

    for page in range(page_range):
        current_page = 1 + page * 10 # 네이버뉴스 URL 1 -> 11 -> 21 -> ...
        crawling_url_list = URL_BEFORE_KEYWORD + key_word + URL_BEFORE_PAGE_NUM + str(current_page)

        # URL에 get 요청 보내기 (>>200 -> 서버에서 잘 처리되어 OK 싸인 전송)
        response = requests.get(crawling_url_list)

        # 응답의 내용을 HTML 코드, 'lxml' 파서를 이용하여 분석
        soup = BeautifulSoup(response.text, 'lxml')

        # HTML 문서에 각 기사의 URL을 갖고 있는 태그 선택 & 저장
        url_tag = soup.select('div.news_area > a')
        # soup.select('상위 태그명 > 하위 태그명)
        # select를 하면 찾은 태그들을 리스트 형태로 반환

        # url_tag에 저장되어 있는 각 기사의 URL을 link 리스트에 저장
        for url in url_tag:
            link.append(url['href']) # href = 연결할 주소를 지정하는 속성

    return link

def get_article(file1, link, key_word, page_range): # 기사를 저장할 파일, 링크, 키워드, 페이지 범위
    print('데이터를 불러오는 중...\n')

    # 기사 내용을 저장할 파일을 쓰기 모드로 열기
    with open(file1, 'w', encoding='utf8') as f:
        i = 1

        for url2 in link:
            article = Article(url2, language='ko') # url2 안에 있는 텍스트 추출

            try:
                article.download() # 기사 다운로드
                article.parse() # 기사 분석
            except:
                print('-', i, '번째 URL을 크롤링할 수 없습니다.')
                continue

            news_title = article.title
            news_content = article.text

            f.write(news_title)
            f.write(news_content)

            i += 1

    # i = 11이 됐을 때 url2에 포함되지 않기 때문에 반복문을 빠져나옴 -> 따라서 출력은 i - 1
    print('- 네이버 뉴스 ' + key_word + ' 관련 뉴스기사', str(page_range) + ' 페이지(기사' + str(i - 1) + '개)가 저장되었습니다. (crawling2.txt)\n')


def wordcloud(filename):
    print('워드 클라우드를 생성하는 중...')

    with open(filename, encoding='utf8') as f:
        data = f.read()

        engine = Okt()
        all_nouns = engine.nouns(data)

        nouns = [n for n in all_nouns if len(n) > 1]
        count = Counter(nouns)

        tags = count.most_common(100)

        img = Image.open('2.png') # 하트 이미지
        mask = np.array(img) # WordCloud mask 적용을 위해서는 이미지를 np.array로 변환해야 함

        wc = WordCloud(font_path='malgun', background_color='white', width=2500, height=1500, mask=mask, colormap='autumn')
        cloud = wc.generate_from_frequencies(dict(tags))

        plt.imshow(cloud, interpolation='bilinear') # interpolation = 이미지 처리 설정(bilinear = 부드럽게, none = 그대로)
        plt.axis('off')
        plt.savefig('cloud.jpg')
        plt.show()

    print('- cloud.jpg가 저장되었습니다.')

file1 = 'crawling2.txt'

key_word = input('키워드를 입력하세요 : ')
page_range = int(input('페이지 범위를 입력하세요 : '))

link = get_link(key_word, page_range)
get_article(file1, link, key_word, page_range)
wordcloud(file1)

영화로 키워드 검색 시의 워드클라우드

Selenium

 

# 인천의 모든 지역에 대한 주유소 정보 엑셀 파일 저장

from selenium import webdriver
import time

driver = webdriver.Chrome('./chromedriver.exe') # selenium 세팅
driver.get('https://www.opinet.co.kr/user/main/mainView.do') # opinet 홈페이지 접속
driver.get('https://www.opinet.co.kr/searRgSelect.do') # opinet 싼 주유소 찾기 링크 접속

local_path = '//*[@id="SIDO_NM0"]' # 시/도 설정 버튼 경로
driver.find_element_by_xpath(local_path).click() # 시/도 설정 버튼 클릭
time.sleep(1)

local_select_path = '//*[@id="SIDO_NM0"]/option[5]' # 시/도 버튼에서 인천 선택 경로
driver.find_element_by_xpath(local_select_path).click() # 시/도 버튼에서 인천 클릭
time.sleep(1)

incheon_list = driver.find_element_by_xpath('//*[@id="SIGUNGU_NM0"]') # 시/군/구 설정 버튼 경로
incheon_list_name = incheon_list.find_elements_by_tag_name('option') # 시/군/구 데이터 추출

gu_name = [option.get_attribute('value') for option in incheon_list_name] # 추출한 시/군/구 데이터 리스트에 저장
gu_name.remove('') # 빈값 삭제

for i in range(len(gu_name)): # 시/군/구 버튼에서 지역 선택 후 엑셀 저장 버튼 클릭을 반복하여 인천 모든 지역의 엑셀 파일 저장
    dong_ele = driver.find_element_by_xpath('//*[@id="SIGUNGU_NM0"]') # 시/군/구 설정 버튼 경로
    dong_ele.send_keys(gu_name[i])

    search_xpath = '//*[@id="searRgSelect"]' # 조회 버튼 경로
    driver.find_element_by_xpath(search_xpath).click() # 조회 버튼 클릭
    time.sleep(2)

    try:
        driver.find_element_by_xpath('//*[@id="templ_list0"]/div[7]/div').click() # 엑셀 파일 버튼 클릭
        time.sleep(2)
    except:
        print(gu_name[i], '페이지를 새로 고침 합니다.')
        driver.refresh() # 페이지 새로 고침
        driver.find_element_by_xpath('//*[@id="templ_list0"]/div[7]/div').click()
        time.sleep(2)

 

댓글