네이버 뉴스 자동 저장 프로그램 소스 코드 분석

이번 글에서는 핵마피아 꼼짝마 프로젝트를 위해 만든 프로그램의 소스 코드를 분석합니다.
[네이버 뉴스 자동 저장 프로그램]에 관한 설명은 이전 글에서 확인할 수 있습니다.

각 단계별 동작 요약

- 일괄 검색 : OR 연산자를 사용하여 검색어 1개라도 포함된 기사를 모두 검색
- 개별 검색 : 검색어를 1개씩 나눠서 기사를 검색하여 기사에 포함된 검색어를 확인
- 검색 결과 병합 : 개별 검색에서 확인한 기사에 포함된 검색어를 일괄 검색 결과에 추가
- 형태소 분석 : 기사 제목의 형태소를 분석하여 많이 사용된 단어를 확인
- 엑셀 저장 : 검색 결과와 형태소 분석 결과를 엑셀 파일에 저장
- 올리기 : 엑셀 파일을 구글 드라이브에 올림


개발 환경

프로그램 개발 언어는 파이썬을 사용했습니다.
파이썬 개발 환경은 윈도우10에 아나콘다를 설치해서 준비했습니다.
아나콘다를 설치하면 함께 설치되는 주피터로 코딩을 했습니다.
추가로 설치한 라이브러리는 아래 <함수 설명>에 정리되어 있습니다.


함수과 라이브러리

naver_news_crawling(keyword, search_period = datetime.today())
일괄 검색 함수 - raquests, BeautifulSoup 라이브러리 사용
naver_news_crawling_oneKeyword(keyword, search_period = datetime.today())
개별 검색 함수 - raquests, BeautifulSoup 라이브러리 사용
news_list_merge(allKeyword_newsList, oneKeyword_newsList)
검색 결과 병합 함수
hangeul_morpheme_analyser(newslist)
행태소 분석 함수 - konlpy 라이브러리 사용
news_list_write_to_exelfile(keyword, newslist, keywords_count, search_period, work_time, errors_count)
엑셀 저장 함수 - openpyxl 라이브러리 사용
google_drive_upload(file_title)
올리기 함수 - 구글 API 사용

구글 API를 사용하려면 라이브러리 설치뿐 아니라 Google Cloud console에서 API를 사용할 수 있도록 설정해야 하며 프로그램이 구글 서비스에 접근하려면 인증코드도 받아야 합니다.
파이썬으로 구글 API를 사용하는 방법은 Python Quickstart 문서에 잘 설명되어 있습니다.

한글 형태소 분석 라이브러리 KoNLPy를 윈도우에 설치하는 방법은 이곳에 설명되어 있습니다.
JPype1 설치에 관한 설명에는 빠져있지만 이미 설치된 파이썬 버전에 맞는 것을 받아야 합니다.
설치 환경이 윈도우 64비트, 파이썬 3.9.7이면 “JPype1-1.4.0-cp39-cp39-win_amd64.whl”를 받아야 합니다.


파이썬 파일(모듈)

검색 함수에서 엑셀 저장 함수까지는 한 파일(모듈)에 작성하고 올리기 함수는 다른 파일에 작성했습니다.
올리기 함수는 어떤 파일이든 상관없이 작동하는 함수로 볌용성을 위해 나누었습니다.

lsj_crawling.py
내장 함수 : naver_news_crawling, naver_news_crawling_oneKeyword, news_list_merge, hangeul_morpheme_analyser, news_list_write_to_exelfile
lsj_google_drive.py
내장 함수 : google_drive_upload
NaverNewsCrawling.py
이 파일을 실행하면 모든 단계를 순차적으로 처리합니다.


lsj_crawling.py 파일 요약

import requests #HTTP 요청 라이브러리
from bs4 import BeautifulSoup #HTTP 문서 분석 라이브러리
from datetime import datetime, timedelta

# 입력된 문자열을 한번에 검색하여 기사 목록을 반환하는 함수
# 검색어 문자열, 현제 년월일시를 매개변수로 받음
def naver_news_crawling(keyword, search_period = datetime.today()):
•••

# 입력된 문자열을 '|' 기호로 나누어 검색어별로 기사를 검색하여 기사 목록을 반환하는 함수
# naver_news_crawling과 다름 점은 기사 목록에 기사별로 포함된 검색어가 추가됨
# 검색어 문자열, 현제 년월일시를 매개변수로 받음
def naver_news_crawling_oneKeyword(keyword, search_period = datetime.today()):
•••

# naver_news_crawling_oneKeyword의 기사 목록에 naver_news_crawling의 기사별 검색어를 추가
# 검색어 정보가 없는 기사 목록과 검색어 정보가 있는 기사 목록을 매개 변수로 받음 
def news_list_merge(allKeyword_newsList, oneKeyword_newsList):
•••

from konlpy.tag import Okt # 한국어 정보처리를 위한 파이썬 패키지(konlpy)에서 Okt(Open Korean Text)를 사용
from operator import itemgetter # 단어 목록을 사용 횟수를 기준으로 내림차순 정렬을 위해 operator의 itemgetter를 사용

# 기사 제목의 한글 형태소를 분석하여 제목들에 포함된 단어들의 목록을 반환하는 함수
# 기사 목록을 매개 변수로 받음
def hangeul_morpheme_analyser(newslist):
•••

import openpyxl # 파이썬에서 엑셀 읽기/쓰기를 가능하게 하는 라이브러리

# 기사 목록과 단어 목록을 엑셀 파일에 저장하고 결과를 반환하는 함수
# 검색어, 기사 목록, 단어 목록, 검색 년월일시, 프로그램 동작 년월일시, 오류 기사 갯수를 매개 변수로 받음
def news_list_write_to_exelfile(keyword, newslist, keywords_count, search_period, work_time, errors_count):
•••

naver_news_crawling 함수

def naver_news_crawling(keyword, search_period = datetime.today()):
    # 두 가지 형태의 년월일 문자열 생성 - 기사 검색 옵션의 기간 설정에 사용
    search_day = search_period.strftime("%Y%m%d")
    search_day_comma = search_period.strftime("%Y.%m.%d")
    
    page_index = 0 # 페이지 번호
    news_list = [] # 검색 결과가 담길 리스트
    news_index = 0 # 기사 순번(건수)
    errors_count = 0 # 기사 정보에 오류가 있는 기사 건수

    # 첫 페이지부터 마지막 페이지까지 반복 처리
    while True:
        # 검색어, 기간, 페이지 번호가 담긴 네이버 뉴스 검색 URL 문자열 생성
        navernews_url = "https://search.naver.com/search.naver?&where=news&query=" + keyword + "&sm=tab_pge&sort=2&photo=0&field=0&reporter_article=&pd=3&ds=" +  search_day_comma + "&de=" + search_day_comma + "&docid=&nso=so:da,p:from" + search_day + "to" + search_day + ",a:all&mynews=0&start=" + str(page_index) + "1&refresh_start=0"
        
#         print(navernews_url) # 디버깅 - URL 내용 확인
        
        # navernews_url로 검색 결과를 HTTP로 요청하고 HTML 문서를 받음
        source = requests.get(navernews_url, headers={'User-Agent':'Mozilla/5.0'}).text
        # HTML 문서 구문 분석
        soup=BeautifulSoup(source, "html.parser")
        
        # 기사 정보가 담긴 영역(태그)들을 선택
        news_area_tags = soup.select("div.news_area")
        
        # 선택된 영역들을 하나씩 분석하여 기사 목록에 추가
        for news_area in news_area_tags:
            # 기사 제목이 담긴 영역 선택
            news_tit_tags = news_area.select("a.news_tit")
            # 언론사 정보가 담긴 영역 검색 - 속성값에 공백이 있어 find_all을 사용
            info_press_tags = news_area.find_all("a", "info press")             
        
            # 기사 제목 또는 언론사 정보가 없는 기사는 제외
            if len(news_area_tags) == 0 or len(info_press_tags) == 0:
                errors_count += 1
                continue
                
            # 짧은 변수명으로 변경    
            news_title = news_tit_tags[0]
            # "언론사 선정" 문구가 담긴 i 영역 선택
            info_press = info_press_tags[0].i        
            
            # i 영역이 있으면 제거 - 이걸 제거하지 않으면 언론사명에 "언론사 선정"이 붙는 문제가 생김
            if info_press:
                info_press.clear()
                
            # 짧은 변수명으로 변경
            press_name = info_press_tags[0]
                
            # 기사 목록에 언론사명, 기사 제목, 기사 URL, 검색어가 들어갈 공백 추가
            news_list.append([press_name.text, news_title['title'], news_title["href"],''])
            
            # 기사 순번 증가
            news_index += 1
            
            # 디버깅 - 기사 목록에 추가된 기사 정보 출력
#             print("{} : {}, {}, {}".format(news_index, press_name.text, news_title['title'], news_title["href"]))

        # for 끝 - 기가 정보가 담긴 영역 분석
        
        # 다음 페이지로 가기 버튼 영역을 선택
        btn_next_tag = soup.select("a.btn_next")
       
        # 다음 페이지 버튼이 없으면 반복 검색을 멈춤
        if not 'href' in btn_next_tag[0].attrs:
            break
            
        # 페이지 번호 증가
        page_index += 1      
        
    # while 끝 - 모든 페이지 검색
    
    # 디버깅 - 기사 검색 결과 출력
#     print('Number of page : ' + str(page_index))
#     print('Number of news : ' + str(news_index))
#     print('Number of error news : ' + str(error_news))
    
    # 기사 목록과 오류가 있는 기가 건수를 반환
    return (news_list, errors_count)

naver_news_crawling_oneKeyword 함수

def naver_news_crawling_oneKeyword(keyword, search_period = datetime.today()):
    # 문자열을 '|' 기호로 분할
    keywords = keyword.replace(' ','').split('|')
    
    search_day = search_period.strftime("%Y%m%d")
    search_day_comma = search_period.strftime("%Y.%m.%d")
    
    keywords_count = [] # 검색어별 기사 건수가 저장될 리스트
    all_news_list = [] # 모든 검색 결과가 담길 리스트
    
    # 검색어별로 기사를 검색
    for one_keyword in keywords:
        page_index = 0
        news_count = 0  
        
        while True:
            navernews_url = "https://search.naver.com/search.naver?&where=news&query=" + one_keyword + "&sm=tab_pge&sort=2&photo=0&field=0&reporter_article=&pd=3&ds=" +  search_day_comma + "&de=" + search_day_comma + "&docid=&nso=so:da,p:from" + search_day + "to" + search_day + ",a:all&mynews=0&start=" + str(page_index) + "1&refresh_start=0"

#             print(navernews_url)

            source = requests.get(navernews_url, headers={'User-Agent':'Mozilla/5.0'}).text
            soup=BeautifulSoup(source, "html.parser")

            news_area_tags = soup.select("div.news_area")

            for news_area in news_area_tags:
                news_tit_tags = news_area.select("a.news_tit")
                info_press_tags = news_area.find_all("a", "info press")

                if len(news_area_tags) == 0 or len(info_press_tags) == 0:
                    continue
               
                news_title = news_tit_tags[0]
                info_press = info_press_tags[0].i
            
                if info_press:
                    info_press.clear()

                press_name = info_press_tags[0]

                # 기사 목록에 언론사명, 기사 제목, 기사 URL, 검색어 추가
                all_news_list.append([press_name.text, news_title['title'], news_title["href"], one_keyword])

                news_count += 1

                # 디버깅 - 기사 목록에 추가된 기사 정보 출력
#                 print("{} : {}, {}, {}".format(news_index, press_name.text, news_title['title'], news_title["href"]))
        
            btn_next_tag = soup.select("a.btn_next")

            page_index += 1 
    
            if not 'href' in btn_next_tag[0].attrs:
                break

        # 검색어별 기가 건수 저장
        keywords_count.append((one_keyword, news_count))
        # 디버깅 - 검색어별 검색 결과 출력
#         print('{} : page-{}, news-{}'.format(one_keyword, page_index, news_count))
    
    # for 끝 - 검색어별 기사 검색
    
    # 기사 목록에서 비교 기준이 되는 기사의 순번
    current_index = 0
    
    # URL를 비교하여 중복 기사를 제거, 지워질 기사의 검색어는 남는 기사에 저장
    while current_index < len(all_news_list):
        # 비교 기준이 되는 기사의 URL
        current_url = all_news_list[current_index][2]
        
        # 비교 대상이 되는 기사의 순번은 비교 기준 기사 순번의 바로 다음 순번부터 마지막 기사까지
        search_index = current_index + 1
        
        # 기준 기사와 URL 같은 대상 기사를 삭제하고 검색어를 기준 기사에 추가
        while search_index < len(all_news_list):
            # 비교 대상이 되는 기사의 URL
            search_url = all_news_list[search_index][2]
            
            # URL이 같을 경우
            if current_url == search_url:
                # 기준 기사에 대상 기사의 검색어 추가
                all_news_list[current_index][3] += ', ' + all_news_list[search_index][3]
                # 대상 기사 삭제
                del all_news_list[search_index]
                
            # URL이 다르면 비교 대상 기사 순번을 다음 기사로 바꿈
            else:
                search_index += 1
                
        # while 끝 - URL이 같은 비교 대상 기사 찾기
        
        # 비교 기준 기사의 순번을 다음 기사로 바꿈
        current_index += 1
    
    # while 끝 - 중복 기사 제거

    # 디버깅 - 총 기가 건수 출력
#     print('Number of news : ' + str(len(all_news_list)))
    
    # 기사 목록과 검색어별 기가 건수를 반환
    return (all_news_list, keywords_count)

news_list_merge 함수

def news_list_merge(allKeyword_newsList, oneKeyword_newsList):
    # 검색어 정보가 없는 기사 목록의 순번
    current_index = 0
    
    # 기준(검색어 정보가 없는) 기사 목록의 모든 기사를 대상(검색어 정보가 있는) 기사 목록에서 찾아 검색어를 추가
    while current_index < len(allKeyword_newsList):
        # 기준 기사의 URL
        current_url = allKeyword_newsList[current_index][2]
        
        # 대상 기사 목록의 순번
        search_index = 0
       
        # 대상 기사 목록의 기사들과 비교
        while search_index < len(oneKeyword_newsList):
            # 대상 기사의 URL
            search_url = oneKeyword_newsList[search_index][2]
            
            # URL이 같다면
            if current_url == search_url:
                # 기준 기사 정보에 검색어를 복사
                allKeyword_newsList[current_index][3] = oneKeyword_newsList[search_index][3]
                # 대상 기사 삭제
                del oneKeyword_newsList[search_index]
                # 대상 기사와 비교 while 반복문 멈추기
                break
            # URL이 다르면
            else:
                # 대사 기사 순번을 다음 기사로 바꿈
                search_index += 1
        
        # while 끝 - 대상 기사와 비교
        
        # 기준 기사 순번을 다음 기사로 바꿈
        current_index += 1 

hangeul_morpheme_analyser 함수

def hangeul_morpheme_analyser(newslist):
    okt = Okt()
    
    news_index = 0 # 기사 번호
    words = {} # 단어와 해당 단어가 포함된 기사 순번이 저장될 딕셔너리
    
    # 모든 기사의 제목을 분석
    for title in newslist:
        # 이번 반복에서 처리하는 기사의 번호
        news_index += 1 
        
        # 기사 제목의 행태소 분석
        pos = okt.pos(title[1])

        # 모든 형태소를 확인하여 필요한 품사만 저장
        for morp in pos:
            # 구두점과 와국어는 제외
            if morp[1] == 'Punctuation' or morp[1] == 'Foreign':
                continue
            else:
                # 이미 저장된 단어이면
                if morp[0] in words:
                    # 기사 번호을 추가
                    words[morp[0]].append(news_index)
                # 저장 안 된 단어이면
                else:
                    # 단어와 기사 번호을 추가
                    words[morp[0]] = [news_index]
                    
         # for 끝 - 품사 확인           
         
    # for 끝 - 기사 제목 분석
    
    len_val = [] # 단어별 기사 건수 리스트
    # 단어별 기사 건수 저장
    for val in words.values():
        len_val.append(len(val))

    # 단어, 기사 건수, 단어가 포함된 기사 번호로 리스트를 만듬
    words_list = list(zip(words.keys(), len_val, words.values()))
    # 단어 목록을 기사 건수로 내림차순 정렬
    words_sort = sorted(words_list, key = itemgetter(1), reverse=True)

    # 디버깅 - 형태소 분석 결과 출력
#     print('Number of words : {}'.format(len(words_sort)))
    
    # 형태소 분석 결과 반환
    return words_sort

news_list_write_to_exelfile 함수

def news_list_write_to_exelfile(keyword, newslist, keywords_count, search_period, work_time, errors_count):
    # 엑셀 자료형 준비
    wb = openpyxl.Workbook()
    
    # 기본으로 생성되는 시트를 모두 제거
    for sheet in wb.sheetnames:
        wb.remove(wb[sheet])
        
    # 기사 검색 날짜을 이름으로 하는 시트를 생성
    ws = wb.create_sheet(title = search_period.strftime("%Y.%m.%d"), index = 0)
    
    # 셀을 합쳐서 제목이 들어갈 공간 준비
    ws.merge_cells('A1:C1')
    # 제목과 기사 검색 날짜 입력
    ws['A1'] = '네이버 뉴스 모니터링 - ' + search_period.strftime("%Y.%m.%d")
    
    # 제목의 글자 크기를 변경
    ws['A1'].font = openpyxl.styles.Font(size = 16)
    
    # 셀을 합쳐서 요약 정보가 들어갈 공간 준비
    ws.merge_cells('A2:C2')
    # 요약 정보 입력
    ws['A2'] = '검색어 : {} / 기사 건수 : {}(오류:{}) / 모니터링 일시 : {}'.format(keyword, len(newslist),errors_count,  work_time.today().strftime("%Y.%m.%d.%H:%M"))

    
    keyword_count_str = '검색어별 기사 건수 : '
    
    # 검색어별 기사 건수를 표시할 문자열 준비
    for key in keywords_count:
        keyword_count_str += '{}({}) '.format(key[0], key[1])
    
    # 검색어별 기사 건수 입력
    ws['A3'] = keyword_count_str
    
    # 열의 제목 입력
    ws['A4'] = '순번'
    ws['B4'] = '언론사'
    ws['C4'] = '제목'
    ws['D4'] = '검색 키워드'
    ws['E4'] = '대응 필요'
    ws['F4'] = '비고'
    
    # 중앙 정렬로 변경
    ws['D4'].alignment = ws['E4'].alignment = ws['A4'].alignment = ws['B4'].alignment = openpyxl.styles.Alignment(horizontal='center')
    
    # 셀의 폭을 변경
    ws.column_dimensions['A'].width = 10
    ws.column_dimensions['B'].width = 20
    ws.column_dimensions['C'].width = 80
    ws.column_dimensions['D'].width = 20
    ws.column_dimensions['E'].width = 15
    
    # 진한 글씨체로 변경
    for col in ['A', 'B', 'C', 'D', 'E', 'F']:
        ws[col + '4'].font = openpyxl.styles.Font(bold = True)
    
    # 기사 순번
    news_index = 0
    # 첫번째 기사 정보가 입력될 행 번호
    rows_index = 5
    
    # 모든 기사 정보를 입력
    for news in newslist:
        # 이번 반복에서 입력할 기사의 번호
        news_index += 1
        
        # 기사 번호, 언론사명, 제목, 기사의 URL, 기사별 검색어를 입력
        ws.cell(row = rows_index, column = 1).value = news_index
        ws.cell(row = rows_index, column = 2).value = news[0]
        ws.cell(row = rows_index, column = 3).value = news[1]
        ws.cell(row = rows_index, column = 3).hyperlink = news[2]
        ws.cell(row = rows_index, column = 3).font = openpyxl.styles.Font(size = 11, color = '000000FF')
        ws.cell(row = rows_index, column = 4).value = news[3]
        ws.cell(row = rows_index, column = 4).font = openpyxl.styles.Font(size = 8)
        
        #1, 2, 5번 열을 중앙 정렬로 변경 
        ws.cell(row = rows_index, column = 1).alignment =  ws.cell(row = rows_index, column = 2).alignment = ws.cell(row = rows_index, column = 5).alignment = openpyxl.styles.Alignment(horizontal='center')
        
        # 행 번호 증가
        rows_index += 1
        
    # for 끝 - 기사 정보 입력
    
    # 모든 기사의 제목에 대해 한글 형태소를 분석
    words = hangeul_morpheme_analyser(newslist)
    # "단어_분석"이 이름인 새로운 시트를 생성
    ws1 = wb.create_sheet(title = search_period.strftime("단어_분석"), index = 1)

    # 열의 제목 입력
    ws1['A1'] = '단어'
    ws1['B1'] = '개수'
    ws1['C1'] = '기사 번호'
    
    ws1['A1'].font = ws1['B1'].font = ws1['C1'].font = openpyxl.styles.Font(bold = True)
    ws1['A1'].alignment = ws1['B1'].alignment = openpyxl.styles.Alignment(horizontal='center')
    
    ws1.column_dimensions['B'].width = 7
    ws1.column_dimensions['C'].width = 40
    
    # 첫번째 단어가 입력될 행 번호
    rows_index = 2
    
    # 모든 단어를 엑셀에 입력
    for word in words:
        # 단어, 기사 건수를 입력
        ws1.cell(row = rows_index, column = 1).value = word[0]
        ws1.cell(row = rows_index, column = 2).value = word[1]
        
        ws1.cell(row = rows_index, column = 1).alignment = ws1.cell(row = rows_index, column = 2).alignment = openpyxl.styles.Alignment(horizontal='center')
        
        # 기사 순번을 담을 문자열 준비
        index_str = ''
        
        # 기사 순번을 쉼표로 구분한 문자열 생성
        for index in word[2]:
            index_str += str(index) + ', '
            
        # 기사 순번 문자열 입력
        ws1.cell(row = rows_index, column = 3).value = index_str
        # 문자 색을 변경
        ws1.cell(row = rows_index, column = 3).font = openpyxl.styles.Font(size = 11, color = '0000B000')
        
        rows_index += 1
        
    # for 끝 - 모든 단어 입력
    
    
    # 기사 검색 날짜가 들어간 파일명으로 엑셀 파일 생성
    file_name = '네이버_뉴스_모니터링_' + search_period.strftime("%Y%m%d") + '.xlsx'
    
    # 엑셀 파일에 자료 쓰기
    save_res = openpyxl.writer.excel.save_workbook(wb, file_name)
    
    # 엑셀 파일 쓰기가 성공했는지 확인하고 결과 출력
    if save_res:
        print('Save the file : ' + file_name)
    else:
        print('Failed to save')
    
    # 엑셀 파일 쓰기 결과와, 파일명을 반환
    return [save_res, file_name]

lsj_google_drive.py 파일

import os.path # 파일/디렉토리 경로 관련 라이브러리
from googleapiclient.discovery import build # Google Discovery 서비스 생성 함수
from google_auth_oauthlib.flow import InstalledAppFlow # Google 인증 절차를 돕는 클래스
from google.auth.transport.requests import Request # Google 인증 초기화에 사용되는 클래스
from google.oauth2.credentials import Credentials # oauth2 인증서 제어용 클래스

SCOPES = ['https://www.googleapis.com/auth/drive.file']

# 입력한 파일을 구글 드라이브에 올리는 함수
# 파일명을 매개 변수로 받음
def google_drive_upload(file_title):
    creds = None

    if os.path.exists('token.json'): # 인증 정보가 담긴 json 파일이 있으면 읽음
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)

    if not creds or not creds.valid: # 인증 정보가 없으면
        if creds and creds.expired and creds.refresh_token: # 인증 정보가 없고 기간이 완료 되었고 갱신 토큰이 있으면
            creds.refresh(Request()) # 인증 정보 갱신
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'client_secret_oauth_navernewscrawling.json', SCOPES) # 사용자 정보가 담긴 json 파일을 인증에 필요한 인스턴스를 생성
            creds = flow.run_local_server(port=0) # 로컬 웹 서버를 시작하고 웹브라우저를 열어서 사용자 인증을 진행
        with open('token.json', 'w') as token: # 인증 정보를 json 파일에 저장
            token.write(creds.to_json())

    service = build('drive', 'v3', credentials=creds) # 구글 드라이브 서비스 객체 생성
    
    #'핵마피아_꼼짝마' 구글 드라이브 폴더 ID
    folder_id = '1QWEyApCN5l123L3pFD98qwer123-3wkt'

    # 파일 올리기에 필요한 매개 변수 전의
    file_name = file_title
    metadata = {'name': file_title,
                'parents' : [folder_id],
                'mimeType': None
                }

    # 구글 드라이브에 파일 올리기 실행
    res = service.files().create(body=metadata, media_body=file_name).execute()
    # 파일 올리기 결과를 출력
    if res:
        print('Uploaded "%s" (%s)' % (file_name, res['mimeType']))
    else:
        print('Failed uploaded : ' +  file_name)

NaverNewsCrawling.py 파일

# 다른 파일에 작성된 함수들 불러오기
from lsj_crawling import naver_news_crawling, news_list_write_to_exelfile, naver_news_crawling_oneKeyword
from lsj_google_drive import google_drive_upload
from datetime import datetime, timedelta

# 기사 검색에 사용할 검색어를 설정
key_word = '원전 | 원자력 | 탈원전 | 방사능 | 사용후핵연료'

# 현제 일시를 확인
work_time = datetime.today()
# 하루 전 일시를 확인
search_period = work_time - timedelta(days = 1)

# 기사 일괄 검색
(news_list, errors_count) = naver_news_crawling(key_word, search_period)
# 기사 개별 검색
(all_news_list, keywords_count) = naver_news_crawling_oneKeyword(key_word, search_period)
# 검색 결과 병함
news_list_merge(news_list, all_news_list)

# 엑셀 파일에 저장
save_res = news_list_write_to_exelfile(key_word, news_list, keywords_count, search_period, work_time, errors_count)

# 엑셀 파일이 생성되었으면
if save_res[0]:
    # 구글 드라이브에 올리기
    google_drive_upload(save_res[1])

라즈 베리파이에 설치

[개일 온라인 저장소 만들기]에 사용한 라즈베리 파이에서 매일 00:05에 실행되도록 설정했습니다.

디렉토리 이동과 프로그램 실행을 한번에 처리하는 쉘 스크립트를 만들었습니다.

NaverNewsCrawling_sh.sh 파일

#! /bin/bash

echo "Naver News Auto Crawling"

ch /home/pi/NNC/
python3 /home/pi/NNC/NaverNewsCrawling.py

파이썬 파일 3개, 사용자 인증용 json 파일, 쉘 스크립트를 라즈베리파이의 같은 디렉토리에 위치하도록 복사합니다.
쉘 스크립트를 리눅스의 작업 스케줄러  cron에 매일 00:05에 실행하도록 아래와 같이 등록하면 모든 설정이 끝납니다.

5 0  * * *   /home/pi/NNC/NaverNewsCrawling_sh.sh

후기

대단히 오랜만에 코딩을 하니 정말 재미있었습니다.
처음엔 기사 목록만을 만들어 내는 프로그램이었습니다.
네이버 뉴스 웹 사이트에서 보는 것보다는 편해서 한동안 그대로 사용했습니다.
한참을 사용하다 보니 있으면 좋을 기능을 생각나서 하나하나 추가했습니다.
기사에 포함된 검색어를 확인하고 싶어 “개별 검색 함수”를 만들었고 중복 기사를 빠르게 넘기려고 “형태소 분석 함수”를 만들었습니다.

더욱 빠르게 중복 기사를 넘기고 싶어 단어 목록의 단어마다 기사들의 유사도를 확인하는 기능도 추가하려 했습니다.

아래 표는 2022년 4월 10일 기사의 형태소 문석 결과입니다.

단어

건수

기사 번호

호기

36

12, 12, 14, 14, 16, 23, 23, 30, 30, 31, 31, 36, 36, 44, 46, 46, 52, 60, 60, 70, 70, 76, 78, 80, 83, 85, 89, 89, 107, 107, 112, 119, 168, 170, 170, 176, 

지진

35

3, 7, 10, 15, 17, 20, 21, 24, 25, 37, 40, 43, 45, 48, 54, 57, 58, 59, 61, 62, 63, 69, 72, 74, 79, 92, 95, 99, 101, 103, 108, 141, 169, 175, 179, 

원전

35

10, 15, 21, 24, 25, 32, 34, 34, 35, 42, 42, 45, 48, 51, 52, 56, 57, 59, 61, 63, 68, 74, 75, 79, 87, 93, 98, 98, 101, 103, 108, 116, 136, 141, 161, 

영덕

33

10, 15, 17, 20, 21, 24, 25, 37, 40, 43, 45, 48, 54, 57, 58, 59, 61, 62, 63, 69, 72, 74, 79, 92, 95, 99, 101, 103, 108, 141, 169, 175, 179, 

3.4

29

3, 7, 10, 17, 20, 21, 37, 40, 43, 45, 54, 57, 58, 59, 61, 62, 63, 69, 72, 74, 79, 92, 95, 99, 108, 141, 169, 175, 179, 

규모

28

3, 7, 10, 17, 20, 37, 40, 43, 54, 57, 58, 59, 61, 62, 63, 69, 72, 74, 79, 92, 95, 99, 103, 108, 141, 169, 175, 179, 

없어

26

7, 10, 15, 21, 24, 25, 37, 43, 45, 48, 54, 57, 58, 59, 61, 63, 69, 72, 74, 79, 92, 101, 103, 141, 169, 179, 

위 표에서 “지진”, “3.4”, “규모”, “없어”는 거의 같은 기사의 제목에 들어간 단어입니다.
기사의 건수가 많은 “지진”을 우선 봤다면 “3.4”, :”규모”, “없어”가 들어간 기사를 보지 않아도 됩니다.
기사 번호를 하나하나 비교해서 같은 기사에 들어간 단어라는 것을 확인하는 건 너무나 힘들었습니다.
그래서 이것마져 자동으로 처리해 주는 기능을 만들려했지만 “일본 후쿠시마 방사능 오염수 방류” 관련 기사가 줄면서 전체 기사 건수가 얼마 안 되어 기존 걸 그대로 썼습니다.
만약 유사한 일을 또 하게되면 만들어 볼 생각입니다.

댓글

이 블로그의 인기 게시물

PC용 열기 배출기 만들기

키보드 키캡 만들기

3.5파이 3극 좌우변환 젠더 만들기

빔프로젝터 렌즈 덮개 원격 제어기

실외에 연등선 설치하기

헤드폰을 헤드셋으로 개조하기 2탄

개인 온라인 저장소 만들기

헤드폰을 헤드셋으로 개조하기 1탄

두 가지 뚜껑 만들기

누전차단기 멀티탭 만들기