[Python] 파이썬 웹크롤링봇 만들기 -5- Telegram API를 통한 새글 알리미 만들기

in #kr6 years ago (edited)

Open

먼저, 매주 한개씩 올린다는 약속을 못지켜서 죄송합니다. 그나마 꾸준하게 10일간격으로 올리던 포스팅을 더 늦게 올렸네요. 힘내서 마지막 포스팅까지 최선을 다해보겠습니다.

이번 포스팅에서 다룰것은 Telegram API key 발급받기Python-telegram-bot 라이브러리 사용법 입니다.

마지막에는 새 글 알림 봇 을 만들어보겠습니다.


BOT API KEY 발급받기

텔레그램 봇을 만들려면, 먼저 텔레그램 계정을 발급받고 BotFather와의 상담?을 통해서 API키를 받아야 합니다.
start_bot
대화상대 검색에 "BotFather"를 검색하여 '/start'를 입력하면 위와 같은 명령어 리스트를 응답해줍니다.

get_api_key
다음은 BotFather 대화창에 '/newbot' 을 입력하면, BotFather가 새 봇의 이름을 요청 합니다.

봇의 이름을 입력해줍니다. 이 봇의 이름은 채팅방에서 보이는 이름이고 나중에 변경이 가능합니다. 지켜야할 규칙은 봇 이름 마지막은 'bot'으로 끝나야 합니다.

저는 'alarm2bot'으로 이름을 입력하였습니다. BotFather가 봇이름을 받아드리면 API KEY를 포함한 응답을 줍니다.
'Use this token to acces the HTTP API:' 에 나타나는 문자열이 API KEY 입니다.
이 KEY는 'aaaaaaaaa:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 와 같이 형태를 가지고 있습니다. 이 KEY는 BotFather에게 재요청하거나 변경할 수 있습니다.

텔레그램 API 라이브러리 설치하기

라이브러리 링크 :https://github.com/python-telegram-bot/python-telegram-bot

Jupyter notebook 셀에서 !pip install python-telegram-bot 입력하여 위 라이브러리를 설치합니다.

import telegram

위 라인을 입력하였을때 에러메시지가 나오지 않았다면, 설치에 성공한 것입니다.

API_KEY = 'aaaaaaaaa:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
bot = telegram.Bot(token = API_KEY)   #bot을 선언합니다.

위와 같이 alarm2bot 이름으로 발급받은 API 키를 API_KEY 에 저장해두고 bot으로 선언해줍니다.

이제 bot 에 어떠한 명령어를 호출한다고 하면, alarm2bot에 연결되어 있는 API_KEY 키와 함께 메시지를 Telegram 서버에 전송하게 됩니다.

여기서 잠시 Telegram 으로 돌아가서 키를 부여 받은 봇이름으로 대화상대를 검색하고, 나의 봇에 말을 걸어봅니다. 저는 alarm2bot에게 말을 "안녕 봇"으로 말을 걸어보았습니다.
send_message

Telegram API를 통해 "안녕 봇"으로 수신한 메시지를 받아보겠습니다.

updates = bot.getUpdates()
updates

[<telegram.update.Update at 0x7f423804ee80>]

getUpdates()를 실행해주면 bot이 가지고 있는 API_KEY 를 통해서 수신한 메시지를 받아옵니다. 이 메시지를 출력해봅시다.

for i in updates:
    print(i)

{'message': {'date': 1519368212, 'delete_chat_photo': False, 'new_chat_members': [], 'text': '안녕 봇?', 'chat': {'type': 'private', 'id': 3*******8 , 'first_name': 'Jinhwan'}, 'photo': [], 'from': {'language_code': 'ko-KR', 'id': 3*******8, 'first_name': 'Jinhwan'}, 'group_chat_created': False, 'message_id': 4, 'entities': [], 'new_chat_photo': [], 'new_chat_member': None, 'channel_chat_created': False, 'supergroup_chat_created': False}, 'update_id': 812204986}

위와 같은 Json형태의 메시지를 확인 할 수 있습니다. 'text' 항목에는 alarm2me 봇에 "안녕 봇?" 메시지가 적혀 있네요. 그리고 제일 중요한 chat_id를 적어 놓습니다.
'chat': {'type': 'private', 'id': 3*******8 에서 id 뒤에 숫자가 바로 chat_id 가 됩니다. 이 chat_id 를 알고 있어야 Telegram API를 통해서 원하는대화창에 메시지를 보낼 수 있습니다.

다음은 봇으로 메시지를 보내 보겠습니다.

chat_id = "3*******8"
bot.sendMessage(chat_id = chat_id, text="안녕하세요, alarm2me 봇입니다.")

sned_testmessage

이제 Telegram API 를 통해 메시지 보내는 법을 알았습니다!

coindesk 새글을 인지하고 텔레그램으로 보내기.

이전 포스팅 파이썬 웹크롤링봇 만들기 -3- 암호화폐뉴스사이트 크롤링 에서 사용 하였던 feedparser를 활용해서 최신글 불러오는 코드를 다시한번 보겠습니다.

import feedparser

feed = feedparser.parse("https://www.coindesk.com/feed/")

coindesk_urls = [] #coindesk url들을 가져와서 저장시켜줄 객체

for entry in feed['entries']:
    coindesk_url.append(entry['link']) 

coindesk_url로 저장되어 있는 url들이 이전에 받았던 글인지 비교해야합니다.
feedparser 로 불러 들였던 url들을 저장했던 것을 다시 꺼내서, old_urls 과 new_url을 구별해 보겠습니다.

with open("coindesk_news.txt", "r", encoding="utf8") as f:
        old_urls = f.read().split("\n")

'https://www.coindesk.com/bitfinex-suspends-sale-select-ico-tokens-citing-sec-concerns/',
( 중략 )
'https://www.coindesk.com/ledgerx-cboe-cftcs-trojan-horse-sec-turf-war/',

이전 포스트부터 실습했던 환경이 동일하다면 coindesk_news.txt 를 그대로 불러 올 수 있습니다. 만약에 경로가 다르거나 Jupyter notebook환경이 달라졌다면 파이썬 웹크롤링봇 만들기 -3- 암호화폐뉴스사이트 크롤링 에 있는 코드를 다시한번 실행 해보시기 바랍니다.

새 글 알림이의 프로세스를 다시한번 정리해보면,

  1. feedparser를 통해서 최신글의 url들을 가져온다.
  2. 최신글의 url이 coindesk_news.txt 저장되어 있던(텔레그램으로 보낸) url과 동일한 것인지를 확인한다.
  3. 만약 coindesk_news.txt 에 저장되어 있지 않은 url이라면, coindesk_news.txt에 추가한다.
  4. requestslxml로 새 글 url에 제목과 본문을 가져온다.
  5. 텔레그램 API로 메시지(제목+본문)를 보낸다.

이제, 새글 알림이 프로세스중 2번 과정에 대한 코드를 작성해 보겠습니다.

new_urls = []
for new in coindesk_urls:
    if new not in old_urls:
        print("Updated", url)
    else:
        pass

"Updated" 새로 업데이트된 url이 출력

새로 보이는 url들만 출력되어 보여짐을 확인하였습니다. 다음에는 print 대신 new_urls리스트에 추가하도록 변경 할 것입니다.

다음은, for 반복문 안에 print 대신 'url 업데이트' 와 '텔레그램 메시지 보내기' 기능이 추가 되어야 합니다. 먼저 old_urls 에 포함되어 있지 않은 url들만 coindesk_news.txt에 추가 저장 하겠습니다. 그리고 뒤에 만들 send_news 함수에 대한 부분을 주석으로만 살짝 언급하겠습니다.

new_urls = []
for new in coindesk_urls:
    if new not in old_urls:
        with open("coindesk_news.txt", "a") as f:
            f.write(new + "\n")
            # news_text = get_news_article(new)
            # bot.sendMessage
    else:
        pass

위의 코드를 실행하면, coindesk_news.txt에 없던 url 들이 추가되어 있음을 확인 할 수 있습니다.

이제 url을 받아서, coindesk 뉴스페이지에 제목과 본문을 추출하고 텔레그램으로 메시지를 보내는 함수를 만들어 보겠습니다.

def get_news_article(url):
    from lxml import html
    import requests
    
    resp = requests.get(url)
    lxml_html = html.fromstring(resp.text)
    
    # 제목 추출하기
    news_title = [lxml_html.find_class("article-top-image-section-inner")[0].text_content().strip() + "\n\n"] ##1
    
    # 본문 추출하기
    lxml_end_point = lxml_html.cssselect("em")
    lxml_end_point = lxml_end_point[0]
    end_parent = lxml_end_point.getparent() 
    
    body_lxml = []
    for elem in end_parent.itersiblings(preceding=True):
        body_lxml.append(elem)
    
    news_body = [t.text_content() for t in body_lxml[::-1]]
    
    news_title.extend(news_body)
    
    return news_title

get_news_article 함수에 coindesk 뉴스 url을 넣으면 제목과 본문이 포함되어 있는 리스트를 리턴합니다.
'# 제목추출하기' 부분은 4장에서 다루었어야 했는데 그냥 넘어갔었네요, 죄송합니다.
제목부분은 개발자도구에서 html 구조를 쉽게 확인 할 수 있습니다.
news_title
lxml에서 find_class로 쉽게 해당 위치를 찾아서 text를 추출할 수 있습니다. 여기서 strip() 은 text_content() 결과에 개행문자("\n")이 붙는 경우가 있어 이를 제거하기 위해 사용하였습니다.

coindesk 뉴스 url 을 하나 넣어서 결과가 제대로 나오는지 살펴봅니다.

news = get_news_article("https://www.coindesk.com/bitcoin-low-fees-why-happening-why-matters/")
print("\n".join(news))

Bitcoin Fees Are Down Big: Why It's Happening and What It Means
26 down to $3.
The average cost of

내친김에 텔레그램으로 메시지를 보내보겠습니다.

bot.sendMessage(chat_id = "3*******8", text=news)

BadRequest Traceback (most recent call last)
(중략 )
BadRequest: Message is too long

이런 한번에 보낼 수 있는 메시지가 제한되어 있습니다. Telegram API 문서를 확인해보니 4000자 정도 제한이 있습니다. 'Current maximum length is 4096 UTF8 characters'

4000자씩 나누어서 메시지를 리스트를 묶어주는 함수를 만들어 보겠습니다.

def text_barn_maker(post_lines, max_char = 4000):
    cumulated = 0
    _text = ''
    text_barn = []
    for t in post_lines:
        trim_t = t.strip(' ')[:max_char] #prevent limit max_char
        cumulated += len(trim_t)

        if cumulated > max_char:
            text_barn.append(_text)
            _text = '' #text initialize

            cumulated = len(trim_t) #new text_part
            _text += trim_t
        else:
            _text += trim_t

    text_barn.append(_text)

    return text_barn

text_barn_maker 함수에 대해서 설명을 하자면, 입력 변수인 post_lines 내의 글자수를 계산해서 max_char의 수를 넘기지 않는 text_barn을 생성합니다.
구체적인 예시를 해보자면, 임의의 리스트 ['ab', 'd', 'ef'] 가 있다고 가정했을대, 최대 3글자 단위로 새로운 리스트로 만든다면, ['abd', 'ef'] 를 생성하는 함수가 되는것 입니다.

이제, 텔레그램으로 메시지를 보내는 send_news 함수를 생성하겠습니다.

def send_news(text_barn):
    print("{} text barn(s)".format(len(text_barn)))
    for text_line in text_barn:
        bot.sendMessage(chat_id = '3*****8', text = text_line))
    print("well sent")

위의 함수는 text_barn로 받은 리스트를 for loop로 순환하면서 구성요소(뉴스 text)을 나누어서 보냅니다.

이제 위의 코드들을 모두 합쳐보겠습니다.

import time
import feedparser
import requests
import telegram 

API_KEY = 'aaaaaaaaa:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'

bot = telegram.Bot(token = API_KEY)   #bot을 선언합니다.

def get_news_article(url):
    from lxml import html
    import requests
    
    resp = requests.get(url)
    lxml_html = html.fromstring(resp.text)
    
    # 제목 추출하기
    news_title = [lxml_html.find_class("article-top-image-section-inner")[0].text_content().strip() + "\n\n"] ##1
    
    # 본문 추출하기
    lxml_end_point = lxml_html.cssselect("em")
    lxml_end_point = lxml_end_point[0]
    end_parent = lxml_end_point.getparent() 
    
    body_lxml = []
    for elem in end_parent.itersiblings(preceding=True):
        body_lxml.append(elem)
    
    news_body = [t.text_content() for t in body_lxml[::-1]]
    
    news_title.extend(news_body)
    
    return news_title


def text_barn_maker(post_lines, max_char = 4000):
    cumulated = 0
    _text = ''
    text_barn = []
    for t in post_lines:
        trim_t = t.strip(' ')[:max_char] #prevent limit max_char
        cumulated += len(trim_t)

        if cumulated > max_char:
            text_barn.append(_text)
            _text = '' #text initialize

            cumulated = len(trim_t) #new text_part
            _text += trim_t
        else:
            _text += trim_t

    text_barn.append(_text)

    return text_barn


def send_news(text_barn):
    print("{} text barn(s)".format(len(text_barn)))
    for text_line in text_barn:
        bot.sendMessage(chat_id = "3*******8", text = text_line) # chat_id는 본인 것으로 변경하셔야 합니다.
    print("well sent")


while True:
    feed = feedparser.parse("https://www.coindesk.com/feed/")
    coindesk_urls = []

    for entry in feed['entries']:
        coindesk_urls.append(entry['link']) 

    with open("coindesk_news.txt", "r", encoding="utf8") as f:
        old_urls = f.read().split("\n")
        
    new_urls = []

     for new in coindesk_urls: #새 URL을 new_urls에 추가
        if new not in old_urls:
            new_urls.append(new)
        else:
            pass
    
    for url in new_urls: #함수를 통해 실제 동작하는 부분
        news_lines = get_news_article(url)
        news_barn = text_barn_maker(news_lines)
        send_news(news_barn)
        with open("coindesk_news.txt", "a", encoding="utf8") as f:
            f.write(url + "\n")
        print("well sent")
    time.sleep(300)

마지막 time.sleep(300) 은 300초 동안 최신글 검색을 쉰다는 것입니다. 너무 잦은 페이지 요청을 하게되면 클라우드페어와 같은 DDOS 방지게이트로 들어갈 수 있기 떄문에 주의해야 합니다.

while True 이후 대해서 설명을 드리면, feedparser로 최신 글 리스트를 받아 coindesk_urls에 저장하고 old_urls에 없는 url들만 new_urls에 추가를 합니다. 마지막 for -loop 부분은 new_urls 을 하나씩 돌아가면서 get_news_article, text_barn_maker 을 거쳐서 적당한 크기의 text들을 담고 있는 news_barn 리스트를 생성합니다.
마지막으로 news_barn 에 담겨 있는 text들을 send_news함수를 통해서 Telegram으로 전송하게 됩니다.

다음 시간에는 AWS 무료 Instance에 해당 코드를 올려서 동작시켜보겠습니다.

고생 하셨습니다!

이전글 보기
[Python] 파이썬 웹크롤링봇 만들기 -1-
[Python] 파이썬 웹크롤링봇 만들기 -2- First Scraper
[Python] 파이썬 웹크롤링봇 만들기 -3- 암호화폐뉴스사이트 크롤링
[Python] 파이썬 웹크롤링봇 만들기 -4- 파싱툴을 활용한 본문 추출하기

Sort:  

좋은글 감사합니다~ 파이선 공부하고싶은 자극이 팍팍 됩니다~ 팔로우 하고 갑니다~

감사합니다.
파이썬 좋습니다^^

파이썬 뉴비로서 대단하다는 말밖에 안나와요ㅎㅎ 시간내서 한줄한줄 읽어보고 싶네요! 응원의 의미로 보팅하고 갑니다!!

감사합니다. 공부할수록 좋은 좋은 언어죠^^

역시나 대단하신
이런 글은 어떻게 작성하시는지
지식이 대단하신거 같습니다.ㅎ

스터디하면서 공부한내용 공유드리는겁니다.
좋은건 나누는게 더 좋지요. 감사합니다.

안녕하세요^^ 혹시 RSS가 없는 사이트 게시판 최신글을 텔레그램으로 보내는 방법도 있을까요??

Coin Marketplace

STEEM 0.30
TRX 0.12
JST 0.034
BTC 63799.64
ETH 3130.40
USDT 1.00
SBD 3.97