공부/Python

[python] beautifulsoup을 이용해 네이버 뉴스 리스트 크롤링을 해보자!

bumcrush 2019. 2. 7. 14:22
반응형

 [Python] beautifulsoup를 이용한 네이버 뉴스 리스트 크롤링

 

*본 포스팅은 개인 학습용으로 만든 크롤러라 완벽한 코드가 아닌 점 알려드립니다.

 

 

 

 

- 네이버 검색창에 키워드를 입력하고 뉴스를 누르면 해당 관련 기사 리스트들이 뜨게 된다.

- 오늘은 저 뉴스 리스트들을 beautifulsoup를 이용하여 크롤링해 오는 것을 포스팅할 것이다.

 
 

<크롤링 할 것>

  • 기사 제목
  • 신문사  
  • 날짜  -> 정제화 작업 필요
  • 내용요약본   -> 정제화 작업 필요
  • 해당 기사 하이퍼링크

 

<프로그램 돌아가는 방식>

  1. 사용자 입력(페이지 수, 검색어, 검색 방식, 시작 날짜, 끝 날짜)  def main
  2. 네이버 뉴스 크롤링 작동  def crawler
  3. 내용, 날짜 정제화      def contents_cleansing  def date_cleansing
  4. 웹크롤링 결과 저장 (리스트 -> 딕셔너리 -> data frame -> 엑셀)

 

 

 

1. beautifulsoup 및 pandas 설치

 

우선 웹크롤링을 위해 필요한 beautifulsoup를 설치해 준다.

아나콘다를 설치하신분들은 anaconda prompt를 

파이썬을 설치하신 분들은 Python 3.7 cmd창을 켜준 뒤 아래와 같이 입력한다.

 

pip install beautifulsoup4

 

크롤링 결과를 dataframe으로 저장을 할 것인데 이때 pandas라이브러리가 필요하다.

따라서 판다스도 설치해준다.

 

pip install pandas

*이미 설치가 되어있으면 넘어가도 된다.

 

 

 

2. 네이버 뉴스 구조 파악하는 법

 

웹크롤링은 html 구조 파악이 반인 것 같다.

크롤링을 할 때 주로 html의 id나 class 이름 값으로 긁어오는데,

이거 하나하나 찾는 것이 일이라면 일이다.

 

이때  '크롬' 브라우저를 사용하면 보다 편하게 구조를 파악 할 수 있다.

 

제목 위에 마우스 포인트를 두고 우클릭 후 검사를 누르면

 

 

해당 html태그가 어떻게 생겨 먹었는지 보여준다.

이를 이용하면 class의 이름이나 id명을 알아내기 편하다.

 

위에 a태그를 잠깐 봐보자.

많은 정보가 들어 있다 ! 

 <a href="http://www.skyedaily.com/news/news_view.html?ID=81051" target="_blank" class="_sp_each_url _sp_each_title

onclick="return goOtherCR(this,

'a=nws*f.tit&amp;r=20&amp;i=8817ca83_000000000000000000015801&amp;g=5510.0000015801&amp;u='+urlencode(this.href));" title="전근대적 정부규제 판치는 한국 ‘제2의 페이커’는 없다">전근대적 정부규제 판치는 한국 ‘제2의 <strong class="hl">페이커</strong>’는 없다</a>

 

링크주소 , 제목이 a태그 안에 들어있다. 

또한 class명인 _sp_each_title인 것을 알 수 있는데,

크롤링 할 때 이 class명을 이용해서 링크주소와 제목들을 가져올 것이다.

 

 

 

3. 코드 설명

 

3-1. import

- 자 이제 본격적으로 코드를 보자!

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

필요한 라이브러리들을 몽땅 import 해준다.

  • bs4 , requests   => 웹크롤링을 위해 필요
  • pandas => 크롤링 결과 df으로 저장할 때 필요
  • datetime => 크롤링 결과를 엑셀로 저장할 것인데, 이 때 저장하는 시간을 엑셀 이름으로 설정하기 위해 필요

  •  

    re => 정규표현식 날짜와 내용요약의 정제화 작업을 위해 필요 

     

     

3-2. main 함수

 

def main():
    info_main = input("="*50+"\n"+"입력 형식에 맞게 입력해주세요."+"\n"+" 시작하시려면 Enter를 눌러주세요."+"\n"+"="*50)
    
    maxpage = input("최대 크롤링할 페이지 수 입력하시오: ")  
    query = input("검색어 입력: ")  
    sort = input("뉴스 검색 방식 입력(관련도순=0  최신순=1  오래된순=2): ")    #관련도순=0  최신순=1  오래된순=2
    s_date = input("시작날짜 입력(2019.01.04):")  #2019.01.04
    e_date = input("끝날짜 입력(2019.01.05):")   #2019.01.05
    
    crawler(maxpage,query,sort,s_date,e_date)
    
main()

사용자로부터 입력 값을 받아와 crawler함수로 넘겨준다.

  • maxpage : 몇 페이지까지 크롤링 할 것 인가   ex> 10페이지까지 크롤링하려면  '10'입력
  • query : 크롤링할 키워드 , 검색어 입력 ex> 포켓몬스터
  • sort : 검색 정렬 방식    ex> 관련도순=0 최신순=1 오래된순=2
  • s_date : 검색 시작 날짜
  • e_date : 검색 끝 날짜

사용자가 입력한 값들은 '3-3. crawler함수'로 넘어가 url의 파라미터 값들이 되는 것이다.

 

 

3-3.  crawler 함수

우선 코드를 보기 전에 각 페이지의 url을 파악해야 한다.

네이버에서 상세검색 옵션을 설정하고 페이지마다 url들을 살펴보면 파라미터값들이 아래와 같이 규칙적으로 바뀌는 것을 알 수 있다. ↓

1페이지

https://search.naver.com/search.naver?where=news&query=%EA%B5%BD%EB%84%A4%EC%B9%98%ED%82%A8&sm=tab_opt&sort=0&photo=0&field=0&reporter_article=&pd=3&ds=2019.01.07&de=2019.01.31&docid=&nso=so%3Ar%2Cp%3Afrom20190107to20190131%2Ca%3Aall&mynews=0&mson=0&refresh_start=0&related=0

 

2페이지

https://search.naver.com/search.naver?&where=news&query=%EA%B5%BD%EB%84%A4%EC%B9%98%ED%82%A8&sm=tab_pge&sort=0&photo=0&field=0&reporter_article=&pd=3&ds=2019.01.07&de=2019.01.31&docid=&nso=so:r,p:from20190107to20190131,a:all&mynews=0&cluster_rank=41&start=11&refresh_start=0

 

3페이지

https://search.naver.com/search.naver?&where=news&query=%EA%B5%BD%EB%84%A4%EC%B9%98%ED%82%A8&sm=tab_pge&sort=0&photo=0&field=0&reporter_article=&pd=3&ds=2019.01.07&de=2019.01.31&docid=&nso=so:r,p:from20190107to20190131,a:all&mynews=0&cluster_rank=55&start=21&refresh_start=0

 

4페이지

https://search.naver.com/search.naver?&where=news&query=%EA%B5%BD%EB%84%A4%EC%B9%98%ED%82%A8&sm=tab_pge&sort=0&photo=0&field=0&reporter_article=&pd=3&ds=2019.01.07&de=2019.01.31&docid=&nso=so:r,p:from20190107to20190131,a:all&mynews=0&cluster_rank=72&start=31&refresh_start=0

  • query는 검색어
  • sort는 검색정렬 방식
  • ds는 검색 시작 날짜
  • de는 끝 날짜
  • start라는 파라미터를 보면 규칙이 있는 것을 알 수 있다. 1페이지는 1, 2페이지는 11 , 3페이지는 21, 4페이지는 31
  • 규칙을 파악해서 식을 만들어보면 (page-1)*10+1 가 되는 것을 알 수 있다.
 
def crawler(maxpage,query,sort,s_date,e_date):

    s_from = s_date.replace(".","")
    e_to = e_date.replace(".","")
    page = 1  
    maxpage_t =(int(maxpage)-1)*10+1   # 11= 2페이지 21=3페이지 31=4페이지  ...81=9페이지 , 91=10페이지, 101=11페이지
    
    while page <= maxpage_t:
        url = "https://search.naver.com/search.naver?where=news&query=" + query + "&sort="+sort+"&ds=" + s_date + "&de=" + e_date + "&nso=so%3Ar%2Cp%3Afrom" + s_from + "to" + e_to + "%2Ca%3A&start=" + str(page)
        
        response = requests.get(url)
        html = response.text
 
        #뷰티풀소프의 인자값 지정
        soup = BeautifulSoup(html, 'html.parser')
 
        #<a>태그에서 제목과 링크주소 추출
        atags = soup.select('.news_tit')
        for atag in atags:
            title_text.append(atag.text)     #제목
            link_text.append(atag['href'])   #링크주소
            
        #신문사 추출
        source_lists = soup.select('.info_group > .press')
        for source_list in source_lists:
            source_text.append(source_list.text)    #신문사
        
        #날짜 추출 
        date_lists = soup.select('.info_group > span.info')
        for date_list in date_lists:
            # 1면 3단 같은 위치 제거
            if date_list.text.find("면") == -1:
                date_text.append(date_list.text)
        
        #본문요약본
        contents_lists = soup.select('.news_dsc')
        for contents_list in contents_lists:
            contents_cleansing(contents_list) #본문요약 정제화
        

        #모든 리스트 딕셔너리형태로 저장
        result= {"date" : date_text , "title":title_text ,  "source" : source_text ,"contents": contents_text ,"link":link_text }  
        print(page)
        
        df = pd.DataFrame(result)  #df로 변환
        page += 10
    
    
    # 새로 만들 파일이름 지정
    outputFileName = '%s-%s-%s  %s시 %s분 %s초 merging.xlsx' % (now.year, now.month, now.day, now.hour, now.minute, now.second)
    df.to_excel(RESULT_PATH+outputFileName,sheet_name='sheet1')

사용자로부터 입력값들을 받아와

페이지 수(maxpage_t)만큼 반복문을 돌리고

beauifulsoap의 select를 이용해서 페이지 마다 크롤링을 할 것이다.

 

atags = soup.select('._sp_each_title')

크롤링 할 값을 select안에 입력한다.

select안에 입력할 것은 클래스명이나 id명이다.

위에 처럼 클래스명으로 가져올 경우 온점(.) 뒤에 클래스 명을 적어준다.   ex> '.클래스명'

만약 id명으로 가져올 경우 우물정(#) 뒤에 id명을 적어준다. ex>'#id명'

 

select의 자세한 설명은 정리가 잘 되어있는 블로그가 있어 링크로 남기겠다.

 

 

크롤링 한 결과를 list에 담고 딕셔너리 형태로 만들고 dataFrame으로 변환을 한다.

 

이 때, 날짜와 본문요약은 id값이나 class명이 지정이 되어있지 않아

정제화 과정이 필요하다.

따라서 date_cleansing , contents_cleansing 함수가 필요하다.

(자세한건 3-4에서 설명)

 

 while문이 한 바퀴가 끝나기 직전에 page수를 10씩 증가 시켜 다음 페이지를 크롤링 해올 수 있게 한다.

(1페이지는 1, 2페이지는 11 , 3페이지는 21, 4페이지는 31... )

 

 

 

 

 

3-4.  날짜 정제화 함수

 

네이버 뉴스 크롤링 하면서 제일 골 때렸던 부분들이다.

주요 이슈는 2가지 이다.

 

첫째, 최신 뉴스와 지난 뉴스의 날짜 표기가 다르다.

 

최신 뉴스는 '몇일 전' 이렇게 뜨고

조금 날짜가 지난 뉴스는 해당 '년월일'이 뜨게 된다.

 

둘째, 날짜가 속한 tag에 id나 class명이 없다.

 

 

노란 박스로 칠한 곳이 날짜가 나오는 태그 부분인데,

바로 연결되는 tag id나 class명이 없다.

바로  날짜 값을 크롤링 할 수가 없는 것이다.

 

따라서 위에 <dd> 태그의 class명으로 태그 안의 내용을 모두 가져와서 (태그 까지 포함된 꼴)

정규표현식을 통한 정제화 과정이 필요하다.

 

#날짜 정제화 함수
def date_cleansing(test):
    try:
        #지난 뉴스
        #머니투데이  10면1단  2018.11.05.  네이버뉴스   보내기  
        pattern = '\d+.(\d+).(\d+).'  #정규표현식 
    
        r = re.compile(pattern)
        match = r.search(test).group(0)  # 2018.11.05.
        date_text.append(match)
        
    except AttributeError:
        #최근 뉴스
        #이데일리  1시간 전  네이버뉴스   보내기  
        pattern = '\w* (\d\w*)'     #정규표현식 
        
        r = re.compile(pattern)
        match = r.search(test).group(1)
        #print(match)
        date_text.append(match)

지난 뉴스와 최근 뉴스의 표현식 패턴이 달라서 하나의 정규식을 쓰니 AttributeError가 계속 떳다.

따라서 두개의 정규식을 사용하기 위해 예외처리를 해주었다.

try문안에는 지난 뉴스의 날짜(2018.1.05)를 정규표현식으로 뽑아냈고

except문에는 최신 뉴스의 날짜(1시간 전)를 정규표현식으로 뽑아냈다.

 

 

3-5.  내용요약 정제화 함수

#내용 정제화 함수 
def contents_cleansing(contents):
    first_cleansing_contents = re.sub('<dl>.*?</a> </div> </dd> <dd>', '', 
                                      str(contents)).strip()  #앞에 필요없는 부분 제거
    second_cleansing_contents = re.sub('<ul class="relation_lst">.*?</dd>', '', 
                                       first_cleansing_contents).strip()#뒤에 필요없는 부분 제거 (새끼 기사)
    third_cleansing_contents = re.sub('<.+?>', '', second_cleansing_contents).strip()
    contents_text.append(third_cleansing_contents)
    #print(contents_text)

내용 요약도 id나 class명이 없어

그 위의  'ul.type01 dl'으로 통째로 가져와 정규표현식으로 필요 부분만 잘라 가져왔다.

 

 re.sub('<dl>.*?</a> </div> </dd> <dd>', ' ',  str(contents)).strip()

=> contents값을 가져와서 <dl>부터 </a> </div> </dd> <dd>까지의 내용들을 모두 ' '(공백처리) 하겠다.  

=> .strip() 앞뒤 공백을 지우겠다.

 

 

3-6.  최종 결과 엑셀로 저장

 

#엑셀로 저장하기 위한 변수
RESULT_PATH ='C:/Users/User/Desktop/python study/beautifulSoup_ws/crawling_result/'  #결과 저장할 경로
now = datetime.now() #파일이름 현 시간으로 저장하기

    # 새로 만들 파일이름 지정
    outputFileName = '%s-%s-%s  %s시 %s분 %s초 merging.xlsx' % (now.year, now.month, now.day, now.hour, now.minute, now.second)
    df.to_excel(RESULT_PATH+outputFileName,sheet_name='sheet1')
    

크롤링한 결과인 df을 엑셀로 저장한다. 

엑셀 이름을 현 시각으로 저장하기 위해 datetime.now()를 사용해서 outputFileName 변수를 만든다.

to_excel() 함수를 이용해서 저장 경로와 파일명인 outputFileName를 입력해준다. 

 

 

4. 결과 실행

자! 시험 삼아 코드를 한번 실행해 보자.

 

 

코드를 실행을 하면 콘솔창에 사용자 입력값을 받기 위해 질문들이 나온다.

차례차례 입력하고 엔터를 누른다.

나는 '페이커'관련 기사들을 크롤링 할 것이다.

2019.01.04부터 2019.01.28까지 관련도순으로  10페이지 크롤링해 올 것이다.

 

실행이 완료되면 해당 경로로 들어가보자!

 

 

짜잔 ! 만든 시간을 이름으로 엑셀파일이 하나 만들어져 있다.

들어가보면!

 

 

 

 날짜, 제목, 신문사, 내용, 링크 순으로 크롤링이 잘된 것을 볼 수 있다.

 

 


 

코드 전문은 github에 올려놓겠다.

 

아래 링크로 들어가서

'Clone or Donwload' - 'Download Zip ' 을 눌러 다운 받으시면 되시겠다.

 

https://github.com/sbomhoo/naver_news_crawling

 

sbomhoo/naver_news_crawling

네이버 뉴스 리스트 크롤링 (naver news crawling). Contribute to sbomhoo/naver_news_crawling development by creating an account on GitHub.

github.com


네이버 뉴스 크롤링 / beautifulsoup 크롤링 / beautifulsoup naver 크롤링 / NAVER 크롤링 / naver news 크롤링 / 파이썬 / 파이썬 크롤링 / 파이썬 네이버 크롤링 / python crawling/ 네이버 뉴스 리스트 크롤링 / beautifulsoup / 

2019/05/28 - [공부/Python] - [python] beautifulsoup을 이용해 네이버 뉴스 전체를 크롤링을 해보자! (네이버 뉴스 크롤링 2)

반응형
  • 현재글[python] beautifulsoup을 이용해 네이버 뉴스 리스트 크롤링을 해보자!