크롤링은 파이썬을 이용해서 했으며 BeautifulSoup을 이용했다.
BeautifulSoup이란? 파이썬 라이브러리로, HTML 및 XML 파일에서 데이터를 추출해준다
코랩으로 코드를 작성하였기에 따로 BeautifulSoup install은 해주지 않았었다.
왜 네이버뉴스를 크롤링을 하는 것인가? 우리가 가장 많이 사용하는 포털 네이버, 네이버에 모든 언론사들이 있기에 한 언론사를 지정하기 보다는 모든 언론사에 대해 분석을 하고 싶어 네이버뉴스를 선택했습니다.
프로젝트의 최종 목표는 "링크를 넣으면" --> "분석해준다" 였기에 크롤링이 필수였죠
크롤링을 해서 추출하는 데이터는 뉴스 제목, 이미지, 본문, 날짜, 기자명, 카테고리, 언론사 였습니다.
먼저 네이버 뉴스를 들어가보겠습니다.
사실 elements을 보면 필요한 데이터의 추출이 쉽게 가능합니다.
우리가 보는 웹은 모두 html로 되어있기에 그 요소만 빼오면 되는 것이죠:)
1. 필요한 라이브러리 import
from bs4 import BeautifulSoup
import requests
import re
response = requests.get(news_link)
html_content = response.text
#뉴스 크롤러 실행
soup = BeautifulSoup(html_content,"html.parser")
뉴스 링크로 HTTP GET요청을 보내고 html내용을 가져오기 위해 requests를 사용했습니다.
가져온 HTML 내용은 BeautifulSoup을 통해 파싱되어 필요한 데이터를 추출하는데 사용했습니다.
2. 뉴스 제목
# 뉴스 제목 가져오기
title_element = soup.select_one('h2.media_end_head_headline') or \
soup.select_one("#ct > div.media_end_head.go_trans > div.media_end_head_title > h2")
title = title_element.get_text(strip=True) if title_element else None
select_one은 BeautifulSoup의 함수로, 주어진 CSS 선택자에 일치하는 HTML 문서 내의 첫 번째 요소를 반환합니다. 네이버뉴스마다 요소가 조금씩 다른 게 있어 두 개 중에 하나에서 제목을 추출하도록 하였으며 예외처리까지 했습니다.
#은 id 선택자, .은 클래스 선택자를 가져오는 것입니다
3. 이미지 URL
# 이미지 URL 가져오기
image_container = soup.select_one("span.end_photo_org img._LAZY_LOADING")
image_url = image_container['data-src'] if image_container and 'data-src' in image_container.attrs else None
4. 뉴스 본문
# 뉴스 본문 가져오기
content_elements = soup.select("article#dic_area") or soup.select("#articeBody")
content = ''.join([str(elem) for elem in content_elements])
# HTML 태그 제거 및 텍스트 정리
clean_content = re.sub('<[^>]*>', '', content)
5. 그 외
# 날짜, 기자 이름, 카테고리, 언론사 가져오기
published_at = soup.find('span', {'class': 'media_end_head_info_datestamp_time'}).text.strip()
byline = soup.find('span', {'class': 'byline_s'}).text.strip()
category = soup.find('em', {'class': 'media_end_categorize_item'}).text.strip()
category = '정치' if category == '총선' else category
provider_img = soup.find('img', {'class': 'media_journalistcard_summary_press_img'})
provider = provider_img['alt'] if provider_img and 'alt' in provider_img.attrs else None
find는 특정 조건에 일치하는 첫 번째 요소를 찾아서 반환합니다. 만약 일치하는 요소가 여러개 있더라도, 첫 번째로 발견된 요소만을 반환합니다. (요즘 총선이라 정치 카테고리를 총선 카테고리라고 찾길래 예외처리를 해주었습니다.)
전체 코드는 아래와 같습니다.
from bs4 import BeautifulSoup
import requests
import re
def Feature(news_link):
# 요청 및 HTML 파싱
response = requests.get(news_link)
soup = BeautifulSoup(response.text, "html.parser")
# 뉴스 제목 가져오기
title_element = soup.select_one('h2.media_end_head_headline') or \
soup.select_one("#ct > div.media_end_head.go_trans > div.media_end_head_title > h2")
title = title_element.get_text(strip=True) if title_element else None
# 이미지 URL 가져오기
image_container = soup.select_one("span.end_photo_org img._LAZY_LOADING")
image_url = image_container['data-src'] if image_container and 'data-src' in image_container.attrs else None
# 뉴스 본문 가져오기
content_elements = soup.select("article#dic_area") or soup.select("#articeBody")
content = ''.join([str(elem) for elem in content_elements])
# HTML 태그 제거 및 텍스트 정리
clean_content = re.sub('<[^>]*>', '', content)
# 날짜, 기자 이름, 카테고리, 언론사 가져오기
published_at = soup.find('span', {'class': 'media_end_head_info_datestamp_time'}).text.strip()
byline = soup.find('span', {'class': 'byline_s'}).text.strip()
category = soup.find('em', {'class': 'media_end_categorize_item'}).text.strip()
category = '정치' if category == '총선' else category
provider_img = soup.find('img', {'class': 'media_journalistcard_summary_press_img'})
provider = provider_img['alt'] if provider_img and 'alt' in provider_img.attrs else None
# JSON 데이터로 변환
news_data = {
"title": title,
"image_url": image_url,
"content": clean_content + f" {byline}",
"published_at": published_at,
"provider": provider,
"byline": byline,
"fix_category": category
}
return news_data
'졸업프로젝트' 카테고리의 다른 글
[졸업프로젝트 회고] 아쉬운점과 개선점 (1) | 2024.07.10 |
---|---|
[MLOps PipLine] model serving 하는 방법 (feat. FastAPI, docker) (3) | 2024.06.29 |
[졸업 프로젝트] FastAPI와 PostgreSQL 연동하기 (feat. GCP로 배포까지) (2) | 2024.05.06 |
[졸업 프로젝트] Bubblow 소개 (7) | 2024.03.29 |