많이 읽은 경제뉴스 텔레그램 알람 봇 만들기 - AWS Lambda - ②

2020. 3. 22. 17:37AWS

 

이번 포스팅에서는 파이썬으로 네이버 경제 뉴스 크롤링을 하도록 하겠습니다.

저는 주피터 노트북을 사용하였습니다.

 

1. 네이버 경제 많이 읽은 뉴스

가장 많이 본 뉴스 메뉴는 경제지면까지 들어가지 않아도 뜨기 때문에 뉴스 기본 페이지에서 크롤링하셔도 됩니다.

  • 네이버 경제면에서 우측에 가장 많이 본 경제 뉴스 TOP 10을 크롤링 하도록 하겠습니다.
  • 다른 포털 사이트나 다른 지면의 뉴스를 원하시는 분들은 코드만 바꿔서 사용하시면 됩니다.

 

 

2. 가져올 목록의 HTML 태그 확인 

우리에게 필요한 기사의 HTML태그가 어떻게 생겼는지, 필요한 태그가 무엇인지 확인합니다.  

  • https://news.naver.com/main/main.nhn?mode=LSD&mid=shm&sid1=101
  • 크롬에서 f12 를 누르시면 개발자 도구창이 우측에 뜹니다.
  • 개발자 도구창을 띄운 상태에서 아래 그림의 1번 표시가 되어 있는 곳을 누르시고 그 상태로 2번 표시가 되어 있는 첫 번째 기사로 마우스를 갖다 대면 아래처럼 코드가 뜨게 됩니다.
  • 3번과 4번에서 보면 알 수 있듯이 첫 번째 기사가 첫 번째 <li> 태그와 대응 되고 나머지 기사들이 차례로 각각 <li> 태그에 대응 된 다는 것을 알 수 있습니다.

 

 

3. 뉴스 크롤링 코드 작성

이제 주피터 노트북에서 코드를 작성해봅니다.

  • 필요한 라이브러리들과 토큰 및 Chat_id 그리고 URL을 변수에 저장합니다.
from bs4 import BeautifulSoup
import telegram
import requests
import datetime
#필요한 라이브러리 

token = "1234565977:AAE?uSv?6zxO?RR?kr8J-qIAt?VOe?Owh?Y" 
#token (1편에서 확인했던 token)
receiver_id = 111111111 
# Chat_id (1편에서 확인했던 Chat_id)
url = 'https://news.naver.com/main/main.nhn?mode=LSD&mid=shm&sid1=101'
	

 

  • 텔레그램으로 메세지를 보내는 코드를 작성합니다.
  • sendMessage 함수의 인수인 chat_id와 message를 텔레그램 bot의 sendMessage의 chat_id, text 변수에 입력합니다. 
def sendMessage(chat_id, message):
    bot.sendMessage(chat_id = chat_id, text=message)

#메세지를 보내는 함수
#chat_id 와 보낼 메세지 내용은 text가 필요하다.

 

  • 네이버 뉴스에서 기사 제목과 URL을 크롤링 해오는 코드를 작성합니다.
  • 왜 그런지는 모르겠으나 텔레그램에 메세지를 보낼 때 리스트 형태로 보내면 한글이 깨져서 전송이 됩니다. 깨지지 않게 하는 방법을 찾다가 한 문장으로 join시키면 깨지지 않아서 마지막 return 값을 join시켰습니다.
def url_title_extract():
    req = requests.get(url)
    html = req.text
    soup = BeautifulSoup(html, 'html.parser')
#URL에서 HTML을 파싱해서 soup에 저장한다. 
    
    eco = soup.find_all('a', {'class':'nclicks(rig.rankeco)'})
#'a' 태그의 'class' 이름이 'nclicks(rig.rankeco)'인 것을 모두 찾아 eco에 저장한다.
#자세한 내용은 아래에서 설명
    
    news_titles = []
#각각의 뉴스 제목을 담을 새로운 리스트를 생성한다.
    
    for idx, new in enumerate(eco):
        news_title = new.get_text()
        news_titles.append('{}.'.format(idx+1) + news_title)
#eco에 담긴 내용을 차례로 하나씩 꺼내와서 get_text()를 통해 텍스트를 추출한다 .
#인덱스+1 값을 앞에 붙여서 1.첫 번째 기사 2.두 번째 기사 꼴을 만들어 준다. 
    
    news_urls = []
#각각의 뉴스 URL을 담을 새로운 리스트를 생성한다.
    
    for new in eco:
        news_url= new.attrs['href']
        news_urls.append(" https://news.naver.com"+news_url)
#eco에 담긴 내용을 차례로 하나씩 꺼내와서 attrs['href']를 통해 URL을 추출한다.
    
    result = []
#제목과 URL을 합칠 결과를 저장 할 리스트를 생성한다.
    
    for i,j in zip(news_titles, news_urls):
        result.append(i + j)
#zip을 사용해 각각의 리스트를 하나로 묶어 result에 저장한다.

    return " ".join(result)
#리스트 형태인 result의 값들을 공백을 기준으로 join시켜주고 그 값을 return한다.

 

중간에 HTML 소스에서 태그와 클래스 이름을 사용해 추출을 하는 과정에서 BeautifulSoup을 사용하여 soup에 담겨있는 html을 prettify로 출력하게 되면 아래와 같이 기사가 묶여 있는 것을 확인 할 수 있습니다.

 

  • <div>태그에 경제지면 뉴스들이 묶여있고 <ul>태그 안에 각 <li>로 기사가 한개씩 묶여있습니다.
  • <li>태그 안에 <span> 태그를 사용하여 class가  "rank num1", "rank num2" ... 인 것들을 가져오게 되면 경제지면 외에 정치, 사회 등의 뉴스들도 같은 class 이름을 갖고 있기 때문에 모든 지면의 뉴스들이 추출되어서 사용 할 수 없습니다.
  • <a>태그를 보면 class 이름이 "nclicks(rig.rankeco)" 라고 되어있는데 끝에 eco라고 붙어 있는 것으로 보아 경제를 뜻하는 것을 알 수있습니다. 실제로 다른 지면의 class이름을 찾아보면 "nclicks(rig.rankpol)", "nclicks(rig.ranksoc)" 와 같이 이름이 다르기 때문에 <a>태그를 활용하여 데이터를 추출하겠습니다.
<div id="ranking_101" style="display:none">
<h5 class="blind">경제</h5>
<ul class="section_list_ranking">
<li><span class="rank num1"><em>1</em></span> <a class="nclicks(rig.rankeco)" href="/main/ranking/read.nhn?mid=etc&amp;sid1=111&amp;rankingType=popular_day&amp;oid=055&amp;aid=0000802237&amp;date=20200321&amp;type=2&amp;rankingSeq=1&amp;rankingSectionId=101" title="이스타항공, 국내선도 운항 중단…대형 항공사도 위기">이스타항공, 국내선도 운항 중단…대형 항공사도 위기</a> </li>
<li><span class="rank num2"><em>2</em></span> <a class="nclicks(rig.rankeco)" href="/main/ranking/read.nhn?mid=etc&amp;sid1=111&amp;rankingType=popular_day&amp;oid=214&amp;aid=0001024673&amp;date=20200321&amp;type=2&amp;rankingSeq=2&amp;rankingSectionId=101" title="공문 다섯 차례 보냈지만…수백 명 모인 '재건축 총회'">공문 다섯 차례 보냈지만…수백 명 모인 '재건축 총회'</a> </li>
<li><span class="rank num3"><em>3</em></span> <a class="nclicks(rig.rankeco)" href="/main/ranking/read.nhn?mid=etc&amp;sid1=111&amp;rankingType=popular_day&amp;oid=025&amp;aid=0002985986&amp;date=20200321&amp;type=1&amp;rankingSeq=3&amp;rankingSectionId=101" title="&quot;제발 한끼는 간단히 먹자&quot;…'돌밥돌밥'에 가열되는 끼니 전쟁">"제발 한끼는 간단히 먹자"…'돌밥돌밥'에 가열되는 끼니 전쟁</a> </li>
<li><span class="rank num4"><em>4</em></span> <a class="nclicks(rig.rankeco)" href="/main/ranking/read.nhn?mid=etc&amp;sid1=111&amp;rankingType=popular_day&amp;oid=437&amp;aid=0000234157&amp;date=20200321&amp;type=2&amp;rankingSeq=4&amp;rankingSectionId=101" title="&quot;비행기 띄울수록 손해&quot; 이스타항공 운행 중단 첫 '셧다운'">"비행기 띄울수록 손해" 이스타항공 운행 중단 첫 '셧다운'</a> </li>
<li><span class="rank num5"><em>5</em></span> <a class="nclicks(rig.rankeco)" href="/main/ranking/read.nhn?mid=etc&amp;sid1=111&amp;rankingType=popular_day&amp;oid=025&amp;aid=0002985997&amp;date=20200321&amp;type=1&amp;rankingSeq=5&amp;rankingSectionId=101" title="국민 재테크가 국민 폭탄 될라…증시 급락에 'ELS 비상'">국민 재테크가 국민 폭탄 될라…증시 급락에 'ELS 비상'</a> </li>
<li><span class="rank num6"><em>6</em></span> <a class="nclicks(rig.rankeco)" href="/main/ranking/read.nhn?mid=etc&amp;sid1=111&amp;rankingType=popular_day&amp;oid=055&amp;aid=0000802233&amp;date=20200321&amp;type=2&amp;rankingSeq=6&amp;rankingSectionId=101" title='"종교 · 실내 체육 등 15일간 중단…지침 어기면 처벌"'>"종교 · 실내 체육 등 15일간 중단…지침 어기면 처벌"</a> </li>
<li><span class="rank num7"><em>7</em></span> <a class="nclicks(rig.rankeco)" href="/main/ranking/read.nhn?mid=etc&amp;sid1=111&amp;rankingType=popular_day&amp;oid=032&amp;aid=0002999092&amp;date=20200321&amp;type=1&amp;rankingSeq=7&amp;rankingSectionId=101" title="‘코로나 치료제 개발’ 제약사가 가짜뉴스에 눈 감는 이유">‘코로나 치료제 개발’ 제약사가 가짜뉴스에 눈 감는 이유</a> </li>
<li><span class="rank num8"><em>8</em></span> <a class="nclicks(rig.rankeco)" href="/main/ranking/read.nhn?mid=etc&amp;sid1=111&amp;rankingType=popular_day&amp;oid=449&amp;aid=0000188681&amp;date=20200321&amp;type=2&amp;rankingSeq=8&amp;rankingSectionId=101" title="9·11 때도 버텼는데…푹푹 쓰러지는 글로벌 ‘실업 대란’">9·11 때도 버텼는데…푹푹 쓰러지는 글로벌 ‘실업 대란’</a> </li>
<li><span class="rank num9"><em>9</em></span> <a class="nclicks(rig.rankeco)" href="/main/ranking/read.nhn?mid=etc&amp;sid1=111&amp;rankingType=popular_day&amp;oid=214&amp;aid=0001024680&amp;date=20200321&amp;type=2&amp;rankingSeq=9&amp;rankingSectionId=101" title="기약 없는 채용 공고…꼭 닫힌 취업문에 토익도 취소">기약 없는 채용 공고…꼭 닫힌 취업문에 토익도 취소</a> </li>
<li><span class="rank num10"><em>10</em></span> <a class="nclicks(rig.rankeco)" href="/main/ranking/read.nhn?mid=etc&amp;sid1=111&amp;rankingType=popular_day&amp;oid=057&amp;aid=0001434956&amp;date=20200321&amp;type=2&amp;rankingSeq=10&amp;rankingSectionId=101" title="중국 500만, 미국 350만 실업 쓰나미…한국도 경제 타격 3~4년">중국 500만, 미국 350만 실업 쓰나미…한국도 경제 타격 3~4년</a> </li>
</ul>
<a class="more_link" href="/main/ranking/popularDay.nhn"><span class="blind">가장 많이 본 뉴스</span>더보기</a>
</div>

 

  • 날짜를 출력하는 코드를 작성합니다.
def date_extract():
    current = datetime.datetime.today()
#datetime.today()를 활용해 현재 날짜와 시간을 current에 저장한다.
    
    today_time = current.strftime("%Y/%m/%d") + " 많이 읽은 경제뉴스 TOP 10"
#datetime 데이터타입인 변수를 "%Y/%m/%d"형태의 문자열 형태로 변환한다.
#문자열 끝에 " 많이 읽은 경제뉴스 TOP 10" 라는 내용을 더해준다.
    
    return today_time
  

 

  • 실행시킬 main코드를 작성합니다.
  • main코드는 이렇게 두셔도 되지만 Lamda에 올릴 때 형태가 조금 바뀌게 됩니다. 추후에 설명하겠습니다.
def main():
    bot = telegram.Bot(token = token)
    today_news = url_title_extract()
#url_title_extract()함수를 이용하여 추출한 제목과 URL을 today_news변수에 저장한다.
    
    date = date_extract()
#date_extract() 함수를 이용하여 추출한 날짜를 date 변수에 저장한다.

    date_news = date, today_news

    for n in date_news:
        bot.sendMessage(chat_id=receiver_id, text=n)
#날짜와 기사제목,URL을 차례로 메세지 전달한다.

 

3. 코드를 추합하여 실행 테스트

from bs4 import BeautifulSoup
import telegram
import requests
import datetime

token = "1068485977:AAE8uSv66zxOERR5kr8J-qIAtMVOeuOwhvY" #token
receiver_id = 984514882 # Chat_id
url = 'https://news.naver.com/main/main.nhn?mode=LSD&mid=shm&sid1=101'


def sendMessage(chat_id, message):
    bot.sendMessage(chat_id = chat_id, text=message)

def url_title_extract():
    req = requests.get(url)
    html = req.text
    soup = BeautifulSoup(html, 'html.parser')
    eco = soup.find_all('a', {'class':'nclicks(rig.rankeco)'})

    news_titles = []

    for idx, new in enumerate(eco):
        news_title = new.get_text()
        news_titles.append('{}.'.format(idx+1) + news_title)
    
    news_urls = []
    
    for new in eco:
        news_url= new.attrs['href']
        news_urls.append(" https://news.naver.com"+news_url)
        
    result = []

    for i,j in zip(news_titles, news_urls):
        result.append(i + j)

    return " ".join(result)
    
    
def date_extract():
    current = datetime.datetime.today()
    today_time = current + datetime.timedelta(hours=9)
    today_time = today_time.strftime("%Y/%m/%d") + " 많이 읽은 경제뉴스 TOP 10"
    return today_time

def main():
    bot = telegram.Bot(token = token)    
    today_news = url_title_extract()
    date = date_extract()

    date_news = date, today_news

    for n in date_news:
        bot.sendMessage(chat_id=receiver_id, text=n)
        
if __name__ == "__main__":
    main()

 

  • 코드를 실행하니 제 핸드폰의 텔레그램으로 뉴스목록이 도착했습니다. 

 

 

 

 

다음 포스팅에서는 AWS-Lambda에 코드를 실행시키는 방법을 포스팅하겠습니다.

 

이전포스팅

2020/03/20 - 많이 읽은 경제뉴스 텔레그램 알람 봇 만들기 - AWS Lambda - ①

 

다음포스팅

2020/03/30 - 많이 읽은 경제뉴스 텔레그램 알람 봇 만들기 - AWS Lambda - ③