본문 바로가기

카테고리 없음

크롤링(Crawling)

728x90
728x90
Reporting Date: May. 25, 2024

 

 


"이스라엘 전쟁" 에 대한  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 .  스크롤이 내려갈 때까지 페이지를 스크롤하고,
그 과정에서 발생하는
새로운 결과를 크롤링하는 코드를 구현하는 데 실패하였다. 

2 .  10 의 배수일 때마다 진행 상황을 출력하지 않고,  불규칙하게 출력한다.


 

 

 

5 .  데 이 터  저 장

수집한 데이터를  DataFrame으로 변환하여 파일로 저장한다.

# 데이터프레임으로 변환 후 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}") # 로그에 크롤링 완료 메세지 기록

 

 

 

문제점:

1 .  데이터프레임을 깔끔하게 저장하는 데 실패하였다. 

2 .  1000의 뉴스가 수집되지 않았다.

더보기
import pandas as pd
import pickle

# CSV 파일 로드
input_file = r"C:\Users\jkl12\개인적 활동\naver_news_war3.csv"
df = pd.read_csv(input_file)

# 긴 문자열을 줄이는 함수 정의
def shorten_text(text, length=20):
    if len(text) > length:
        return text[:length] + '...'
    return text

# 데이터프레임 정리
# 예: 'date', 'title', 'content' 컬럼만 선택하고 'date' 컬럼을 기준으로 정렬
df_cleaned = df[['date', 'title', 'content']].sort_values(by='date')

# 문자열 길이를 줄임
df_cleaned['title'] = df_cleaned['title'].apply(shorten_text)
df_cleaned['content'] = df_cleaned['content'].apply(shorten_text)

# 정리된 데이터프레임 출력
print(df_cleaned.head())
print(df_cleaned.tail())

 

naver_news_war3.csv
2.90MB

 


 

728x90
반응형