Always awake,

[2탄] 쉽게 따라하는 네이버 뉴스 크롤링(python) - title, URL 가져오기 본문

코딩

[2탄] 쉽게 따라하는 네이버 뉴스 크롤링(python) - title, URL 가져오기

호재 P.B 2020. 8. 30. 22:31

"본 포스팅은 네이버 웹 크롤링 실제 python 코드를 작성하는 2탄입니다.

전 단계인 수행계획을 확인하고 싶으신 분들은 아래링크(1탄)을 참고해주세요 :)"

네이버 웹 페이지 구성이 바뀌어 내용, 코드 수정(2021.01.26)

 

 

쉽게 따라하는 네이버 뉴스 크롤링(python) - 1탄

데이터 분석을 공부하면서 python의 여러 패키지를 툴로 사용하였고 그 중 데이터 수집 단계에서 사용되는 크롤링에 대해서 알게 되었다. 크롤링(crawling)은 단어 그대로 해석하면 밑바닥까지 긁어

everyday-tech.tistory.com

 

지난 편에 이어 네이버 뉴스를 크롤링하는 python 코드를 작성하는 포스팅을 합니다.

 

지난 편은 네이버 뉴스 웹 구성 체계를 확인하여 원하는 정보(뉴스 기사 title, URL)의 위치를 확인하여 크롤링 계획을 수립하였고

본 편에서는 이를 python 코드로 작성하여 실제 수행해보려고 합니다.


서론

이에 앞서 지난 편을 간단히 정리하고 본 편에서 진행할 내용을 정리해봅니다.

 

<지난 편>

 

STEP 1. 소스 조사

  1. 제공 사이트 조사
  2. 제공 정보 조사
  3. 확보 가능 정보 확인

STEP 2. 웹 구성 체계 확인

  1. HTML 구조 확인
  2. 제공 정보 확인
  3. 크롤링 가능 여부 확인

 

<본 편>

 

STEP 3. 크롤링 진행

  1. parsing 방안 및 위치 확인
  2. request 방법 확인
  3. data 저장 형태 설계

STEP 4. 최종 데이터 생성

  1. 데이터 저장 형태
  2. 데이터 저장

 

이를 바탕으로 실제 수행하는 코드를 작성해보겠습니다

 


본론

STEP 3. 크롤링 진행

1. 필요 환경 및 패키지 설치

  • python 3 버전을 사용하였습니다.
  • requests, bs4, re, pandas 패키지를 사용하였습니다.
패키지 명 용도
requests 웹페이지 소스 추출(HTML)
bs4 HTML 파싱, 필요 태그 및 소스 추출
re 조건부 문자열(정규 표현식), 태그 탐색 시 일반화 조건을 사용하기 위함
pandas 데이터 프레임, 엑셀 변환

 

2. 코드 작성

1. 패키지 importing

import requests
from pandas import DataFrame
from bs4 import BeautifulSoup
import re
from datetime import datetime
import os

 

2. 현재 시간 저장

  • 나중에 output으로 엑셀 저장 시 크롤링한 날짜, 시간을 파일명에 넣기 위해 저장하는 변수입니다.
date = str(datetime.now()) 
date = date[:date.rfind(':')].replace(' ', '_') 
date = date.replace(':','시') + '분' 

 

3. Input 생성

  • 검색할 키워드, 추출할 뉴스 기사 수를 저장하는 변수입니다.
  • query에서 ' ' 를 '+'로 바꾸어주는 이유는 띄어쓰기 시 URL 조건 절에 '+'로 적용되어 요청 인자가 들어가기 때문입니다.
query = input('검색 키워드를 입력하세요 : ') 
query = query.replace(' ', '+') 

news_num = int(input('총 필요한 뉴스기사 수를 입력해주세요(숫자만 입력) : ')) 

 

4. 요청할 URL 생성 및 요청

  • 3번에서 받은 키워드(query)를 URL의 조건절 중 키워드에 해당하는 변수에 대응시켜 요청 URL을 만듭니다.
  • 그리고 requests 패키지의 get함수를 이용하여 HTML 코드를 받아옵니다.
  • 받은 코드를 bs4의 BeautifulSoup 함수를 이용하여 파싱합니다.
news_url = 'https://search.naver.com/search.naver?where=news&sm=tab_jum&query={}'

req = requests.get(news_url.format(query))
soup = BeautifulSoup(req.text, 'html.parser')

받은 HTML 코드의 일부를 보면 다음과 같습니다.(req.text)

5. 원하는 정보를 담을 변수 생성(딕셔너리)

 

뉴스 기사 정보를 저장할 딕셔너리를 생성합니다. (key : 번호, value : 뉴스 기사 정보)

 

  • idx : 현재 뉴스의 번호
  • cur_page : 네이버 뉴스의 웹 페이지입니다. 추출하려는 기사 수가 현재 페이지에 있는 기사보다 많은 경우 다음 페이지로 넘어가야 하기 때문에 현 페이지 번호를 기억하도록 변수로 설정한 것입니다.
news_dict = {} 
idx = 0 
cur_page = 1

 

6. parsing 한 HTML 코드에서 원하는 정보 탐색(뉴스 기사 title, URL)

 

idx(현재 뉴스 기사 번호)가 news_num(원하는 뉴스 기사 수) 보다 작은 동안 아래 코드를 실행합니다.

  • table : 뉴스 바운딩 박스(ul 태그)
  • li_list : 뉴스 바운딩 박스 안의 각 뉴스 기사(li 태그)
  • area_list : 뉴스 기사 안의 뉴스 제목, 본문이 담긴 태그(div 태그) 
  • a_list : 각 뉴스기사 내부 title, URL 정보가 담긴 태그(a 태그)
  • news_dict : 뉴스 기사를 담는 딕셔너리
    • key : 뉴스 기사 번호
    • value : 뉴스 기사 title, url을 key로 하는 딕셔너리
  • next_page_url : 현재 수집한 뉴스 기사 수가 부족한 경우 다음 페이지로 넘어가야 하므로 다음 페이지에 해당하는 URL을 추출합니다.
    • 형식은 div 태그이며 class 속성 값이 "sc_page_inner"입니다.
      • 하위에 존재하는 a 태그 내부에 페이지 번호와, URL(href 속성 값) 정보가 있습니다.
    • 위에서 언급한 cur_page 변수와 일치하는 페이지 번호의 URL을 가져옵니다. 

print()
print('크롤링 중...')

while idx < news_num:
### 네이버 뉴스 웹페이지 구성이 바뀌어 태그명, class 속성 값 등을 수정함(20210126) ###
    
    table = soup.find('ul',{'class' : 'list_news'})
    li_list = table.find_all('li', {'id': re.compile('sp_nws.*')})
    area_list = [li.find('div', {'class' : 'news_area'}) for li in li_list]
    a_list = [area.find('a', {'class' : 'news_tit'}) for area in area_list]
    
    for n in a_list[:min(len(a_list), news_num-idx)]:
        news_dict[idx] = {'title' : n.get('title'),
                          'url' : n.get('href') }
        idx += 1

    cur_page += 1
    
    pages = soup.find('div', {'class' : 'sc_page_inner'})
    next_page_url = [p for p in pages.find_all('a') if p.text == str(cur_page)][0].get('href')
    
    req = requests.get('https://search.naver.com/search.naver' + next_page_url)
    soup = BeautifulSoup(req.text, 'html.parser')

 

STEP 4. 최종 데이터 생성

7. 데이터 프레임 변환 및 저장

  • 크롤링한 뉴스 정보가 담긴 딕셔너리(news_dict)를 데이터 프레임(news_df)으로 변환합니다.
  • 그리고 크롤링한 키워드(query)와 크롤링 날짜(date)를 엑셀 파일 명으로 하여 저장합니다.
  • 마지막으로 저장을 완료한 폴더를 띄웁니다.
print('크롤링 완료')

print('데이터프레임 변환')
news_df = DataFrame(news_dict).T

folder_path = os.getcwd()
xlsx_file_name = '네이버뉴스_{}_{}.xlsx'.format(query, date)

news_df.to_excel(xlsx_file_name)

print('엑셀 저장 완료 | 경로 : {}\\{}'.format(folder_path, xlsx_file_name))
os.startfile(folder_path)

 

8. 결과물

결과물은 다음과 같습니다.(query : 코로나 강아지 산책, news_num : 17)

 


전체 코드

import requests
from pandas import DataFrame
from bs4 import BeautifulSoup
import re
from datetime import datetime
import os

date = str(datetime.now())
date = date[:date.rfind(':')].replace(' ', '_')
date = date.replace(':','시') + '분'



query = input('검색 키워드를 입력하세요 : ')
news_num = int(input('총 필요한 뉴스기사 수를 입력해주세요(숫자만 입력) : '))
query = query.replace(' ', '+')


news_url = 'https://search.naver.com/search.naver?where=news&sm=tab_jum&query={}'

req = requests.get(news_url.format(query))
soup = BeautifulSoup(req.text, 'html.parser')


news_dict = {}
idx = 0
cur_page = 1

print()
print('크롤링 중...')

while idx < news_num:
### 네이버 뉴스 웹페이지 구성이 바뀌어 태그명, class 속성 값 등을 수정함(20210126) ###
    
    table = soup.find('ul',{'class' : 'list_news'})
    li_list = table.find_all('li', {'id': re.compile('sp_nws.*')})
    area_list = [li.find('div', {'class' : 'news_area'}) for li in li_list]
    a_list = [area.find('a', {'class' : 'news_tit'}) for area in area_list]
    
    for n in a_list[:min(len(a_list), news_num-idx)]:
        news_dict[idx] = {'title' : n.get('title'),
                          'url' : n.get('href') }
        idx += 1

    cur_page += 1

    pages = soup.find('div', {'class' : 'sc_page_inner'})
    next_page_url = [p for p in pages.find_all('a') if p.text == str(cur_page)][0].get('href')
    
    req = requests.get('https://search.naver.com/search.naver' + next_page_url)
    soup = BeautifulSoup(req.text, 'html.parser')

print('크롤링 완료')

print('데이터프레임 변환')
news_df = DataFrame(news_dict).T

folder_path = os.getcwd()
xlsx_file_name = '네이버뉴스_{}_{}.xlsx'.format(query, date)

news_df.to_excel(xlsx_file_name)

print('엑셀 저장 완료 | 경로 : {}\\{}'.format(folder_path, xlsx_file_name))
os.startfile(folder_path)

아쉬운 점

뉴스 본문까지 크롤링 가능하였다면 더 좋았을 것 같습니다.

추출한 뉴스 기사 URL로 다시 requests 요청을 보내고 받은 HTML 코드 내부에 기사 본문이 있기 때문에 현재 기술로는 충분히 가능합니다.

 

하지만, 각 언론사마다 웹페이지 구성이 다르기 때문에 본문의 정보를 담고 있는 태그의 위치와 이름 등이 상이하여 모든 언론사의 뉴스 본문 크롤링을 하기에는 더 많은 조사가 필요합니다.

 

만약 일부 언론사( ex. 매일 경제, 동아 일보, 중앙일보 )만 크롤링한다면

  • 각 언론사 웹페이지 구성을 파악하고
  • 본문의 위치를 확인한 후에
  • 뉴스 크롤링 시 해당 언론사 URL만 남기고 본문 크롤링을 진행하면 될 것입니다.

마치며

최대한 이해가 쉽도록 주저리주저리 설명했는데.. 지루하게 느끼신 분들도 있을 것 같습니다.

긴 글 읽어주셔서 감사합니다.

피드백은 언제나 환영입니다~

 

 

글이 도움이 되셨다면 아래 클릭 한번 부탁드립니다 :)

반응형