개발 공부/Libraries for Python

[Selenium] 셀레니움으로 네이버 뉴스 크롤링 하기

뚜덩ㅇ 2022. 7. 12. 10:38
반응형

 

전체 코드는 아래 깃헙 사이트에 올려두었습니다.

https://github.com/chaehyun29/myRepository/blob/main/DmcProject/Crawling/crawling_naver_news

 

GitHub - chaehyun29/myRepository

Contribute to chaehyun29/myRepository development by creating an account on GitHub.

github.com

 

프로젝트 중 필요한 데이터를 모으기 위해 Selenium을 이용하여 네이버 뉴스를 크롤링 하기로 했다.

 가지고 있는 기본 기업 리스트 데이터 중 기업 명을 네이버 뉴스에 검색하여 뜨는 뉴스 중, 네이버 뉴스로 연결 되는 링크만 들어가서 뉴스 제목, 시간, 본문, 해당 링크를 가져와 csv 파일로 저장 하는 식이다.

 

기본 준비

 

크롬을 이용하여 크롤링을 하기 위해 크롬 드라이버가 필요하다. 우선 크롬 창 오른쪽 위 점 세개를 누르고 도움말->Chrome 정보를 누른다.

 

 

사용하고 있는 크롬 버전을 확인한다.

 

https://chromedriver.chromium.org/downloads

 

ChromeDriver - WebDriver for Chrome - Downloads

Current Releases If you are using Chrome version 104, please download ChromeDriver 104.0.5112.29 If you are using Chrome version 103, please download ChromeDriver 103.0.5060.53 If you are using Chrome version 102, please download ChromeDriver 102.0.5005.61

chromedriver.chromium.org

 

크롬 드라이버를 다운받기 위해 위 사이트에 접속하여 사용중인 크롬 버전과 맞는 드라이버를 다운 받는다.

 

 

다운 받은 드라이버는 압축을 풀어 chromedriver.exe 파일을 아는 곳에 저장 해 둔다.

 

 

코드

실제로 쓰게 될 import 부 이다.

import pandas as pd

# WEB CRAWLING
from selenium import webdriver
from selenium.webdriver.common.by import By

 

셀레니움 사용을 위한 옵션 설정이다. 앞에서 다운받았던 크롬 드라이버의 경로를 미리 path에 넣어 두었다.

WEBDRIVER_PATH = f"C:\chromedriver.exe"
WEBDRIVER_OPTIONS = webdriver.ChromeOptions()

WEBDRIVER_OPTIONS.add_argument("headless")

아래 headless 옵션은 창을 띄우지 않는 옵션이다. 코드를 짤 때에는 주석 처리 해놓고 프로그램이 완성되어 실제 크롤링을 할 때는 활성화해두면 창이 안떠 크롤링 속도가 비교적 빨라진다.

 

 

URL생성

def naverNewsUrl(keyword, date_date=None):
    base_url = f"https://search.naver.com/search.naver?where=news&sm=tab_pge&"
    
    if date_date is None:
        queryParams = urlencode({
            quote_plus('query'): keyword,
        }, encoding='utf-8')           
    else:
        queryParams = urlencode({
            quote_plus('query'): keyword,
            quote_plus('ds'): unquote(date_date[:10]),
            quote_plus('de'): unquote(date_date[10:]),
        
        }, encoding='utf-8')
      
    url = base_url + queryParams
    return url

네이버 뉴스 검색 기본이 되는 URL을 base_url에 넣고 날짜구간이 있는 경우 날짜 부분이 추가된다. 

 

네이버 뉴스에서 날짜 구간을 넣으면 url에서 ds와 de 값에 날짜 값이 들어가는 것을 확인 할 수 있다. ds 는 date start, de는 date end 로 시작값과 끝값이 각각 들어간다.  그러므로 2020.06.102020.07.10으로 입력 받았다면 앞뒤 10개씩 끊어 ds, de에  넣어준다.

 

크롤링

def crawler_naver_news(company_index, company_name, date_date = None):

검색명으로 company_name을 사용할 것이고 date_date로 검색할 뉴스의 날짜 구간을 받았다. 프로젝트 상의 이유로 기업에게 부여한 인덱스 번호를 csv파일로 저장할 때 파일 명에 넣을 것이고 그 외 인자는 아직 사용되지 않는다.

 

logging.basicConfig(filename='./Logs/crawler_naver_news.log', level=logging.ERROR)

에러 로그를 남기기 위한 코드 부분이다. 지정한 파일 명으로 로그가 남는다.

 

news_urls = []
news_titles = []
news_dates = []
news_contents = []

크롤링하여 받은 링크, 뉴스제목, 날짜, 본문 내용을 담을 리스트를 선언해주었다.

webDriver = webdriver.Chrome(options=WEBDRIVER_OPTIONS, executable_path=WEBDRIVER_PATH)
url = naverNewsUrl(keyword=SrchKeyword, date_date=date_date)
webDriver.get(url)
time.sleep(1)

앞에서 설정해둔 경로와 옵션을 이용하여 웹드라이버와 url을 생성해주고  get()을 이용하여 해당 url을 열어준다. 열리는데 시간이 걸리기 때문에 time.sleep()로 기다려준다. 위 headless 옵션을 활성화하여 창을 안띄우는 경우 기다릴 필요가 없으므로 주석처리하거나 지워주자.

 

css selector를 이용하여 원하는 요소를 가져올 건데 버전에 따라 selector를 사용하는 방법이 다르다.

# css_selector = 'div.news_wrap.api_ani_send > div > div.news_info > div.info_group > a.info'
# ems = webDriver.find_elements_by_css_selector(css_selector)
ems = webDriver.find_elements(By.CSS_SELECTOR,'a.info')

주석처리를 한  find_elements_by_css_selector()는 이전 버전이고 find_elements()가 현재 버전인 듯 하다. 정확하진 않지만 파이썬 3.7부터 find_elements_by_css_selector()가 안먹힌다. (3.6까진 되는거같다...)

이름에서 알 수 있든 find_elements()는 요소들을 리스트로 받을 수 있다. 요소 하나만 받을거라면 복수형임을 알려주는 s가 빠진 find_element() 로 가져올 수 있다.

크롬에서 F12를 누르고 (형광색칠된) 화살표를 누르면 마우스로 가리키는 요소가 쓰여진 부분을 알려준다. 클릭을 하면 해당 코드 부분을 펼쳐 보여준다.

 네이버에 검색되는 뉴스는 기사 제목을 누르면 해당 언론사의 홈페이지에 게시된 기사 링크로 이동된다. 그리고 오른쪽 네이버뉴스를 클릭하면 네이버 뉴스에서 해당 기사를 게시한 링크로 이동된다. 언론사 홈페이지로 들어가 크롤링을 하려고 하면, 일단 언론사가 굉장히 많은데 언론사 홈페이지 별로 제목과 본문 내용을 담아두는 방식이 모두 다르기 때문에 크롤링하기 매우 까다로워진다. 그래서 네이버 뉴스로 연결되는 기사만 가져오기로 했다. 그래서 그 부분을 확인해 보니 a.info 라는 곳에 담아 놓은 것을 확인 할 수 있다. 그리고 현재 페이지에 있는 기사들을 모두 들어가 볼 것이기 때문에 find_elements(a.info) 를 이용하여 요소들을 가져왔다.

그런데 가져온 요소들을 보면 네이버 뉴스 뿐 아니라 언론사 이름 요소도 들어가 있다. 그래서 판별하여 링크로 접속했다.

if len(ems) == 0 : break
            for em in ems:
                if em.text == '네이버뉴스': # 언론사 홈페이지 말고 네이버 뉴스만 가져오기
                    em.click()                        
                    #새창으로 드라이버 전환
                    webDriver.switch_to.window(webDriver.window_handles[1])

                    current_url = webDriver.current_url
                    news_urls.append(current_url) # 본문 링크 수집    

                    # 뉴스 타입 분류
                    news_type = current_url.split('.')[0].split('//')[1]       
                    news_titles.append(webDriver.title.split('::')[0])# 본문 기사 제목 수집

가져온 요소는 요소.click() 으로 이동할 수 있다. 이때 주의 할 점은 처음 생성한 webDriver 네비게이터는 처음 get 한 링크를 바라보고있다. 그러므로 새로 연 링크로 변경하려면 driver.switch_to.window()로 스위칭 해줘야 한다. webDriver.window_handles[0]은 처음 연 그 윈도우고, 그 다음 하나를 열었으니 1로 스위칭 해줬다. 

스위칭한 윈도우, 즉 지금 네비게이터가 보고 있는 링크 url은 webDriver.current_url 에 담겨 있다.

네이버 뉴스는 스포츠 뉴스, 연예 뉴스, 일반 뉴스 세가지가 따로 관리되고 있어서 각각 요소들이 담긴 곳이 다르다. 그래서 뉴스 타입을 구분해 줬다. 그리고 네이버 뉴스는 기사 제목이 해당 url의 타이틀이 되기 때문에 제목은 간단하게 driver.title로 가져왔고 ::뒤에 불필요한 정보가 붙어서 제거해 줬다.

 

연예,스포츠, 그 외로 분류 했고 그 중 연예 뉴스 부분을 보자

역시 요소를 가져오기 위해 포인터를 이용했다. 아까처럼 em에 들어간 모든 요소가 아닌 해당 값만 가져오고 싶기 때문에 해당 부분을 오른쪽 클릭 -> Copy -> Copy selector 를 이용하면 해당 요소의 정확한 selector 값을 가져올 수 있다.

if news_type == 'entertain':
    css_selector = '#content > div.end_ct > div > div.article_info > span > em'
    news_dates.append(webDriver.find_element(By.CSS_SELECTOR,css_selector).text) # 본문 기사 게시 일자 및 시간 수집

    css_selector = '#content > div.end_ct > div > div.end_body_wrp'
    news_contents.append(webDriver.find_element(By.CSS_SELECTOR,css_selector).text) # 본문 기사 본문 내용 수집

복사한 selector를 CSS_SELECOR를 이용한 find_element() 에 넣고 텍스트로 뽑아온다. 같은 방식으로 기사 본문도 가져오고 스포츠 뉴스, 일반 뉴스도 가져온다.

 

 

webDriver.close()
webDriver.switch_to.window(webDriver.window_handles[0])

크롤링이 끝났다면 현재 연 기사를 닫고 다시 처음 열었던 윈도우로 드라이버를 스위칭 해준다. 현재 페이지 기사를 다 크롤링 했다면 다음 페이지로 넘어가는 버튼을 찾아보자.

css_selector = '#main_pack > div.api_sc_page_wrap > div > a.btn_next'
btn_next = webDriver.find_element(By.CSS_SELECTOR,css_selector)

is_disabled = True if btn_next.get_attribute("aria-disabled") == 'true' else False

if is_disabled : break
btn_next.click()
pageNo += 1

버튼이 활성화 상태인지 아닌지 확인하는 enable()메서드가 있지만 왜인지 이곳에선 먹히지 않아 다른 방법을 사용했다. aria-disabled 요소에 버튼의 비활성화 여부가 들어있는 것을 확인하여 이 값을 꺼내 사용했다. 버튼이 비활성화(aria-disabled = "true") 면 루프를 빠져 나가고 아니면 클릭하여 다음 페이지로 넘어간다.

 

 

 

if len(news_dates) != 0:
    news_data = zip(news_dates, news_titles, news_contents, news_urls)
    news_cloumns = ['Date', 'Title', 'Content', 'URL']
    append_csv(company_index, company_name, news_data, news_cloumns)

지금까지 긁어온 데이터들을 zip()으로 붙여주고 컬럼 명을 붙여 csv 저장해준다.

 

 

csv 파일로 저장

def append_csv(company_index, company_name, news_data, news_cloumns):
    
    logging.basicConfig(filename='./Logs/append_csv_log.log', level=logging.ERROR)
    
    try:
        df = pd.DataFrame(news_data)
        df.columns = news_cloumns
        df.to_csv(f'.\\company_news_data\\{company_name}_news_list_{company_index}.csv',encoding='utf-8-sig',index=False)

    except Exception as e:
        print('append csv Error: {}'.format(e))
        logging.error(traceback.format_exc())
        return "error"

 

pandas를 이용해 csv로 저장해주었다. 이때 파이썬에서 \ 하나는 명령어로 인식하기 때문에 \\ 처럼 두개를 붙여주어야 경로가 제대로 들어간다. 또 한글이 들어가기 때문에 encoding을 'utf-8-sig' 로 지정해 주었다.

반응형