"이스라엘 전쟁" 에 대한 1 개월 기간의 네이버 뉴스 기사들을 크롤링하여 뉴스 제목과 내용을 수집하고, 이를 CSV 파일로 저장하는 작업을 수행하였다.
코드는 크게 로깅 설정, 크롤링 함수 정의, 크롤링 실행, 데이터 저장의 4 단계로 나누어져 있다.
1 . Python libraries
import requests # 웹 페이지 내용 가져오기
import logging # 로그 메세지 기록 및 관리
import pandas as pd # 데이터 구조화 및 분석을 위한 데이터 프레임 생성
from tqdm import tqdm # 진행 상황을 시각적으로 표시
from bs4 import BeautifulSoup # HTML 문서 파싱 및 원하는 정보 추출
import pickle # 직렬화
import time # 시간 관련 작업에 사용
2 . 로 깅 설 정
크롤링 작업의 진행 상황 및 오류를 기록한다.
# 함수: logging.basicConfig(): logging 모듈에서 기본적인 로깅 설정을 구성
logging.basicConfig(filename = 'crawler.log', # 1. 로그 파일의 이름
level = logging.INFO, # 2. 로깅의 심각도 수준을 설정: 정보성 메세지
format = '%(asctime)s - %(levelname)s - %(message)s') # 3. 출력형식
3 . 크 롤 링 함 수 정 의
3 - 1 . get_news_urls 함수 : 뉴스 기사 URL들을 추출한다.
# 함수로 크롤링 작업 분리
def get_news_urls(query, page):
# 이스라엘 전쟁 (1개월) url
url_template = ("https://search.naver.com/search.naver"
"?where=news&query={query}&sm=tab_opt&sort=0&photo=0&field=0&pd=2"
"&ds=&de=&docid=&related=0&mynews=0&office_type=0"
"&office_section_code=0&news_office_checked=&nso=so:r,p:1m"
"&is_sug_officeid=0&office_category=0&service_area=0&start={page}")
# 각각을 매개변수로 받은 값으로 대체
# query: 검색 키워드 / page: 페이지를 넘어갈 때 사용
# url_template: 네이버 뉴스 검색 URL 템플릿
url = url_template.format(query=query, page=page)
# HTTP 요청 헤더: 사용자 에이전트(User-Agent)를 설정하여 봇으로 인식됨을 방지
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"}
# 해당 url에 GET 요청을 보냄
response = requests.get(url, headers=headers)
# 오류를 처리하는 방법 정의: 예외 발생
response.raise_for_status()
# 응답 본문을 파싱하여 BeautifulSoup 객체로 변환
# 이 객체를 사용하여 HTML 문서 탐색 및 필요한 정보를 추출
soup = BeautifulSoup(response.text, "html.parser")
# CSS 선택자를 사용하여,
# a 태그 중 클래스가 info(뉴스 기사 링크)인 모든 요소를 찾기.
# 리스트 컴프리헨션을 사용하여,
# 각 요소의 href 속성 값을 추출한 뒤, URL 리스트 만듦.
urls = [a['href'] for a in soup.select("a.info")]
# 지정된 검색 키워드 및 페이지에서 발견된
# 뉴스 기사들의 URL이 담긴 리스트 반환
return urls
3 - 2 . get_news_content 함수 : 주어진 뉴스 기사 URL에서 제목과 본문 내용을 추출한다.
# 뉴스 제목과 내용 관련 함수
def get_news_content(url):
try:
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"}
# timeout=10: 요청이 10초 이상 걸리면, 타임아웃을 발생하도록 설정
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
title = soup.select_one(".media_end_head_headline") # 기사 제목
content = soup.select_one("#dic_area") # 기사 본문
# 제목과 본문이 모두 존재하는지 확인
if title and content:
# 텍스트 내용을 추출 및 앞뒤 공백을 제거하여 반환
return title.text.strip(), content.text.strip()
# try에서 발생한 예외 처리: 오류를 로그에 기록
except requests.exceptions.RequestException as e:
logging.error(f"Failed to crawl {url}: {e}")
# 예외가 발생하거나 제목 & 내용이 없을 경우: 둘다 None으로 처리
return None, None
4 .크 롤 링 작 업
query &total_pages 설정을 통해 실행된다.
# 변수 초기화
query = "이스라엘 전쟁"
total_pages = 100 # 스크롤 대신 사용: 임의의 수 100으로 진행
# 크롤링 진행 상태 표시: tqdm 함수
# as pbar: tqdm 객체를 pbar로 참조하여 진행 상황을 업데이트
with tqdm(total=total_pages) as pbar:
news_data = []
page = 1
# 한 페이지에 10개의 뉴스가 표시된다고 가정
# total_pages * 10으로 설정하여 1000개의 뉴스를 수집하도록 설정
while page <= total_pages * 10:
# 현재 페이지에서 뉴스 기사 URL을 수집
news_urls = get_news_urls(query, page)
# URL 리스트가 비어 있으면 (즉, 더 이상 뉴스 기사가 없으면) 루프를 종료
if not news_urls:
break
# 수집한 각 뉴스 기사 URL에 대해 루프를 실행
# 함수를 호출하여 뉴스 기사 제목과 본문을 수집
for news_url in news_urls:
title, content = get_news_content(news_url)
# 제목과 본문이 모두 존재할 경우에만 데이터 저장
if title and content:
# 수집한 데이터를 news_data 리스트에 딕셔너리 형태로 추가
# 현재 날짜와 시간을 추가
news_data.append({"date": time.strftime("%Y-%m-%d %H:%M:%S"),
"title": title, "content": content, "url": news_url})
# 다음 페이지로 이동
# 한 페이지에 10개의 뉴스가 있다고 가정하고, 10을 더해준다
page += 10
# tqdm의 진행 상황을 1만큼 업데이트
pbar.update(1)
# 수집한 뉴스 데이터의 수가 10의 배수일 때마다 진행 상황을 출력
if len(news_data) % 10 == 0:
print(f"진행 상황: {len(news_data)} 개의 뉴스 수집 완료")
time.sleep(1) # 서버 부하를 줄이기 위해 1초 대기
문제점 :
1 . 스크롤이 내려갈 때까지 페이지를 스크롤하고, 그 과정에서 발생하는 새로운 결과를 크롤링하는 코드를 구현하는 데 실패하였다.
# 데이터프레임으로 변환 후 pickle 파일로 저장
df = pd.DataFrame(news_data)
with open("news_data3.pkl", "wb") as f:
pickle.dump(df, f)
# 저장한 pickle 파일 불러오기
with open("news_data3.pkl", "rb") as f:
loaded_df = pickle.load(f)
# CSV로 저장
output_file = "naver_news_war3.csv"
loaded_df.to_csv(output_file, index=False, encoding='utf-8-sig')
logging.info(f"크롤링 완료: {output_file}") # 로그에 크롤링 완료 메세지 기록