크롤링 (5), beautifulsoup4로 네이버 기사 크롤링하기

네이버 랭킹 뉴스 페이지가 개편되어 해당 코드로 기사를 가져올 수 없습니다. 해당 코드는 참고용으로만 봐주시기 바랍니다. (JY) 융합연구 1 - 크롤링 (4), beautifulsoup4로 네이버 기사 크롤링하기

dsbook.tistory.com

네이버 랭킹 뉴스만 볼 수 있던 페이지가 전면적으로 개편되면서 위 게시글에서 작성한 코드를 사용했을 때 뉴스를 가져올 수 없었다. 그래서 이번에 개편된 랭킹 뉴스 페이지를 기반으로 새롭게 크롤링하는 코드를 작성해보았다.

 

랭킹 뉴스 페이지에서 이전처럼 정치, 경제, 사회, 과학 등 분야별로 따로 나누어서 게시글을 찾아볼 수 없었다. 대신 각 언론사별로 많이 본 뉴스와 댓글이 많은 뉴스를 나누어서 볼 수 있었다. 그래서 언론사 페이지에 들어가면 url은 다음과 같은 형식으로 이루어져 있다.

"https://news.naver.com/main/ranking/office.nhn?officeId=" + press_ID[press] + "&date=" + str(date)

이렇게 각 언론사별로 ID가 생성되어 있어서 랭킹을 보고 싶은 언론사의 ID를 입력하면 해당 언론사의 랭킹뉴스 페이지로 넘어가진다. 또 date 속성에 "20210128"처럼 8글자로 된 날짜를 입력하면 해당 날짜의 랭킹뉴스 페이지로 넘어가진다. 이를 이용해 우리는 원하는 언론사, 원하는 날짜의 랭킹 뉴스 페이지를 크롤링 해올 수 있다.

import re
import requests
import pandas as pd
import time
from bs4 import BeautifulSoup

url = "https://news.naver.com/main/ranking/office.nhn?officeId=214&date=20210114"
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"}
resp = requests.get(url, headers=headers)
soup = BeautifulSoup(resp.text, "html.parser")

만약 ConnectionError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response')) 와 같은 error가 발생한다면 위와 같이 headers를 입력해 get의 headers 인자로 전달해주자.

 

아래와 같이 각 언론사 별 ID를 dict 자료형을 이용하여 초기화했다.

press_ID = {"MBC":"214", "KBS": "056", "SBS": "055", "JTBC": "437"}

 

해당 페이지에서 날짜, 랭킹, 언론사, 주소, 제목, 조회수, 댓글 수를 모두 모아 사전 형태로 만들어 리스트에 추가한 뒤, 각 요소의 주소값을 돌며 새롭게 본문의 내용을 가져와 사전에 추가한다. 그렇게 만들어진 리스트를 DataFrame으로 만들고 csv 파일 형태로 저장한다. 완성된 코드는 다음과 같다.

# demo v0.4 날짜를 입력받아 자동으로 크롤링
import os
os.chdir(r"C:\Users\cjy89\NLP\Project_news_crawling\Naver")

from bs4 import BeautifulSoup
import time
import pandas as pd
import requests
import re

press_ID = {"MBC":"214"}
#, "KBS": "056", "SBS": "055", "JTBC": "437"}

"""
def clean_text(text):
    content = text.get_text()
    cleaned_text = re.sub('[a-zA-Z]', '', content)
    cleaned_text = re.sub(
        '[\{\}\[\]\/?.,;:|\)*~`!^\-_+<>▶▽♡◀━@\#$%&\\\=\(\'\"ⓒ(\n)(\t)]', ' ', cleaned_text)
    cleaned_text = cleaned_text.replace(
        "🇲\u200b🇮\u200b🇱\u200b🇱\u200b🇮\u200b🇪\u200b", "")
    cleaned_text = cleaned_text.replace("오류를 우회하기 위한 함수 추가 ", "")
    cleaned_text = cleaned_text.replace("무단전재 및 재배포 금지", "")
    cleaned_text = cleaned_text.replace(
        "동영상 뉴스                       뉴스데스크 ", "")
    cleaned_text = cleaned_text.replace(
        "동영상 뉴스                       뉴스투데이 ", "")
    cleaned_text = cleaned_text.replace("앵커  ", "")
    return cleaned_text
"""

# 8자리로 된 날짜를 입력하면 해당 날짜의 ranking news를 가져온다.
def get_ranking_news(date):
    total_time = 0
    
    # 언론사 순
    for press in press_ID:
        start = time.time()
        url = "https://news.naver.com/main/ranking/office.nhn?officeId=" + press_ID[press] + "&date=" + str(date)
        headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"}
        resp = requests.get(url, headers=headers)
        soup = BeautifulSoup(resp.text, "html.parser")

        ranking_box = soup.find_all(class_="rankingnews_box_inner")
        l = []

        # 조회수 -> 댓글 수
        for ranking_type in range(2):
            ranking = ranking_box[ranking_type].find_all(class_="list_ranking_num")
            url_list = ranking_box[ranking_type].find_all(class_="list_content")

            # 랭킹 순 (1 ~ 20)
            for rank in range(20):
                d = {}
                d['Date'] = int(date)
                d['Press'] = press
                d['Rank'] = ranking[rank].get_text()
                d['URL'] = url_list[rank].find('a')['href']
                d['Title'] = url_list[rank].find('a').get_text()
                if (ranking_type == 0):
                    d['View'] = url_list[rank].find(class_="list_view").get_text()
                elif (ranking_type == 1):
                    d['Comment'] = url_list[rank].find(class_="list_comment nclicks('RBP.dcmtnwscmt')").get_text()
                l.append(d)

        # 본문 가져오기
        for news in l:
            resp = requests.get("https://news.naver.com" + news['URL'], headers=headers)
            soup = BeautifulSoup(resp.text, "html.parser")
            contents = soup.find(id="articleBodyContents").get_text()
            news['Content'] = re.sub('[\{\}\[\]\/?\(\);:|*~`!^\-_+<>▶▽♡◀━@\#$&\\\=\'\"ⓒ(\n)(\t)]', ' ', contents)
            # news['Content'] = clean_text(content)

        df = pd.DataFrame(l)
        title = press + "/" + str(date) + "_" + press + "_ranking_news.csv"
        df.to_csv(title, sep=",", index=False, encoding="utf-8-sig")
        end = time.time()
        total_time += end - start
        print("Crawling " + str(date) + " " + press + " news :", end - start)
    print("Total time :", total_time)
    print("Average time : ", total_time/len(press_ID))
    print("───────────────────")
# 21.01.01 ~ 21.01.24 크롤링
for i in range(24):
    get_ranking_news(20210101+i)

to_csv로 데이터 프레임 형태를 파일로 저장할 때, title에 해당되는 파일의 이름을 자유롭게 바꿀 수 있다. 또한 원하는 언론사 페이지 뉴스를 크롤링해오고 싶으면 press_ID 변수에 원하는 언론사의 코드를 추가하면 된다.

위 코드를 이용해서 크롤링해왔을 때 하루 동안 한 언론사 페이지를 긁어오는데 평균적으로 10초의 시간이 걸리는 것을 확인할 수 있다. 네이버 뉴스에 기재되는 언론사의 개수는 총 72개인 것을 생각하면 하루동안 전체 언론사의 페이지를 크롤링해오는데 걸리는 시간은 약 720초, 12분으로 상당한 시간이 걸린다. 사람마다 보는 언론사가 다 다르다고 하더라도 대중적으로 선호하거나 대부분의 사람들이 알만한 언론사는 따로 있으므로 이를 감안하여 크롤링해오는데 걸리는 시간을 단축할 수 있을 것이다.

728x90
반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 라이프코리아트위터 공유하기
  • shared
  • 카카오스토리 공유하기