본문 바로가기
꾸준히

[TIL-04] GSMTP에 대해서, 효율적인 코드 짜기

by 무자비한 낭만주먹 2024. 1. 3.

[그림1]. 오늘도 감사한 공부 시작 ~

 

목차
0. 오늘의 학습 시간
1. 오늘 배운 것
2. 감사한 일

 

0. 오늘의 학습 시간
[스파르타 내일 배움 교육]

09:00 ~ 24:00: 약 15시간

 

1. 오늘 배운 것
1. 기능별로 메소드 쪼개보자
: 오늘 하루종일 좋은 코드를 짜려고 머리를 싸맸다. 근데 시간의 한계가 있어 2시간 전부터 기능 구현에 전념해 기능은 만들었다. 그래도 나름 함수별로 기능을 최대한 줄였는데 아직도 아쉽다. 프로젝트가 끝나고 주말에 조금 손을 봐야할 거 같다.

 

from module import naver_news_cr, summary_module, save_news_data

if __name__ == '__main__':
    ''' 
        3가지 절차를 통해 네이버 스포츠 뉴스 크롤링, 본문 요약, db 적재를 하는 모듈이다.
        1. module.naver_news_cr.get_news(): 뉴스 데이터 크롤링 해오는 메서드 (뉴스 관련된 모든 정보가 들어간 list 반환)
        2. module.summary_module.apply_summary: 뉴스 제목과 본문 정보 보내주면, 요약된 문장을 리턴
        3. module.save_news_data: 크롤링 및 전처리한 데이터 보내면 doc 형태로 묶어 haromony.news_data.news_data에 저장
    '''
    news_data = naver_news_cr.get_news() # 1. 각 뉴스 관련된 모든 정보들을 리스트 형태로 합쳐 반환
        
    for news in news_data: # 각 뉴스에 접근
        if len(news[3]) > 990:       # naver-cloud-summary api의 가용치가 1000글자여서, 이에대한 처리
            news[3] = news[3][0:990]
        news_summary = summary_module.apply_summary(news[0], news[3])   # 2. 제목과 본문 보내면 요약된 문장 리턴
        save_news_data.put_news(news[0], news_summary, news[2], news[4], news[1], news[5])  #3. 전처리한 데이터 전달, haromony.news_data.news_data에 저장
    
    print('process success')

 

 

#!/usr/bin/env python3
#-*- codig: utf-8 -*-
import sys
import requests
import json


def apply_summary(title, content):
    '''
        param:
            - title: 기사 제목 정보, 네이버측 클로바 모델에 인풋 값으로 title이 필요해 인자로 받음
            - content: 기사 본문 내용, 네이버 클로바 모델의 인풋 값으로 contnet가 필요해 인자로 받음
        return:
            -  response.text.split(':\"')[1][:-2]: 본문 내용 요약 정보 리턴
    '''
    # api 관련 세팅 #
    client_id = "니거 넣으삼"
    client_secret = "니거 넣으삼"
    headers = {
        "X-NCP-APIGW-API-KEY-ID": client_id,
        "X-NCP-APIGW-API-KEY": client_secret,
        "Content-Type": "application/json"
    }
    language = "ko" # Language of document (ko, ja )
    model = "news" # Model used for summaries (general, news)
    tone = "2" # Converts the tone of the summarized result. (0, 1, 2, 3)
    summaryCount = "5" # This is the number of sentences for the summarized document.
    url= "https://naveropenapi.apigw.ntruss.com/text-summary/v1/summarize"
    data = {   ## 정리 내용 data에 모두 담아줌 line: 45의 requests에 담아 보내줌 ##
        "document": {
        "title": title,
        "content" : content
        },
        "option": {
        "language": language,
        "model": model,
        "tone": tone,
        "summaryCount" : summaryCount
        }
    }
    # print(json.dumps(data, indent=4, sort_keys=True)) # data에 올바르게 data가 적재되었는지 테스트
    
    
    # 위 세팅한 정보를 url 경로(line:29)로 post 요청
    response = requests.post(url, data=json.dumps(data), headers=headers)
    rescode = response.status_code

    # 요약 성공 여부에 따른 분기 처리
    if(rescode == 200): 
        return response.text.split(':\"')[1][:-2].replace('\\n', '  ').replace('\\\"', '') # 성공시 요약 정보 리턴, (깔끔한 데이터를 위해 약간의 전처리)
    else:
        print("Error : " + response.text)

 

 

from module.mongo_con import conn_mongo_news

db = conn_mongo_news()

def put_news(title, summary, image_url, news_url, explain, write_time):
    '''
    param:
        - title: 본문 제목                  [FROM naver_news_cr.py]
        - summary: 본문 요약 정보           [FROM summary_module.py]
        - image_url: 기사 이미지 url 주소   [FROM naver_news_cr.py]
        - news_url: 뉴스의 본래 url 주소    [FROM naver_news_cr.py]
        - explain: 뉴스에 대한 간략한 소개   [FROM naver_news_cr.py]
        - write_time: 작성 시간             [FROM naver_news_cr.py]
    return:
        - db 적재 성공 여부 [True || False]
    '''
    
    post_count = db.news_data.estimated_document_count()    # news_data 문서 개수 가져와 count로 사용
    if post_count == 0:                                     # 컬렉션에 문서가 있을 때와 없을 때 분기 처리
        max_value = 1
    else:
        max_value = db.news_data.find_one(sort=[("post_id", -1)])['post_id'] + 1  # 가장 큰 post_id에서 하나를 더 해줌
    
    doc = {
        'post_id': max_value,
        'title': title,
        'summary': summary,
        'image_url': image_url,
        'news_url': news_url,
        'explain': explain,
        'write_time': write_time
    }
    
    try: #DB 삽입 성공 여부에 따른 분기 반환 [True || False]
        db.news_data.insert_one(doc)
        return True
    except:
        return False

 

 

from bs4 import BeautifulSoup
from bs4 import BeautifulSoup
from selenium import webdriver
from time import sleep

def get_page_html(url, time=0):
    '''
    param: 
    '''
    # 옵션 생성
    options = webdriver.ChromeOptions()
    options.add_argument("headless") # 창 숨기는 옵션 추가
    driver = webdriver.Chrome('./chromedriver', options = options)  # 드라이버를 실행합니다.
    driver.get(url)
    sleep(time)  # 요청받은 시간 만큼 대기
    
    req = driver.page_source  # html 정보를 가져옵니다.
    driver.quit()  # 정보를 가져왔으므로 드라이버는 꺼줍니다.

    return req

def get_news():
    '''
    네이버 뉴스에서 제목과 내용 가져와서, app.py의 news_get라우터 함수에 리턴
    :param: None
    :return:
        - title: 기사 제목
        - img_src: 기사 이미지 주소
        - detail_sentence: 기사 외부 설명 글
        - detail_uri: 기사 url
        - sentence: 기사 본문 전체 내용
        - detail_time: 기사 작성 시간
    '''
    
    
    ## 크롤링 세팅 ##
    req = get_page_html('https://sports.news.naver.com/ranking/index', 2)
    soup = BeautifulSoup(req, 'html.parser')  # 가져온 정보를 beautifulsoup으로 파싱해줍니다.
    news = soup.select('#_newsList > ul > li')
    news_box = []
    error_image = 'https://user-images.githubusercontent.com/68278903/167756503-6c7fd836-65e3-42f5-a4a2-289bc7472e94.jpg'

    ## 크롤링 작업 ##
    for target in news:
        # target 요소 찾기 #
        title = target.select_one('div.text > a').text
        sentence = target.select_one('div.text > p').text
        # img_src = target.select_one('a > img').attrs['src']
        detail_uri = target.select_one('div.text > a').attrs['href']
        
            # 각 뉴스 세부 내용 크롤링: 위에서 수집한 detail_uri 타고 2중 스크래핑 #
        req = get_page_html(f'https://sports.news.naver.com/{detail_uri}', 2)
        detail_soup = BeautifulSoup(req, 'html.parser')  # 가져온 정보를 beautifulsoup으로 파싱해줍니다.
        detail_time = detail_soup.select_one('#content > div > div.content > div > div.news_headline > div > span:nth-child(1)').text
        detail_sentence = detail_soup.select_one('#newsEndContents').text.split('기사제공')[0].replace('\n', '').replace('\t', '')

        try:
            img_src = detail_soup.select_one('#newsEndContents > span:nth-child(1) > img')['src']
        except:
            img_src = detail_soup.select_one('#newsEndContents > span:nth-child(1) > img')['src']
            
        if img_src == None:
            print(f'cant scrapy img: https://sports.news.naver.com/{detail_uri}')
            img_src = error_image
        # print(img_src)
        news_box.append([title, sentence, img_src, detail_sentence, f'https://sports.news.naver.com/{detail_uri}', detail_time.split('기사입력 ')[1]])
        img_src = None

    return news_box

 

 

2. 모듈화
: 오늘 느낀게 하나 있는데 함수 사이의 연관성을 줄이면 줄일수록 유지보수가 쉬워진다. 사실 기능을 몇개 추가하다보니 시스템이 금방 커져서 내가 쓴 변수명도 눈에 잘 들어오지 않았다. 물론 변수명을 잘 지어야 겠지만 사실 더 큰 프로젝트에 투입되면 그런것 보다 구조적이거나 객체지향을 잘 이해해야 도움이 될 거 같다. 함수 자체의 의존성을 줄여서 관련 기능에 문제가 생기면 그 모듈만 보면 되게끔 코딩하는게 중요하다는 생각을 했다.

 

2. 감사한 일

 

사실 오늘 하루종일 눈 빠지게 짠 코드를 가독성이 높게 설계 하지 못했다는 사실이 어떻게 보면 자존심이 상할 일일수도 있지만 반대로 생각하면 "객체지향을 공부해야 겠다"라는 인사이트를 얻을 수 있는 기회임을 깨닫고 오늘 하루도 너무 감사했다.

 

 

오늘 하루도 공부할 수 있어 크게 감사합니다

2022-04-21 개발자 최찬혁