본문 바로가기

공부/Programing

[Python] 크롤링하여 텔레그램 챗봇으로 메세지 보내기 - 1

 전에는 관심도 없던 주식에 갑자기 관심이 생겼다.

주식은 정보 싸움.. 이라고 하길래 시사나 뉴스를 자주 보게 되었는데, 어떻게 하면 더 빨리 정보를 찾을 수 있을까 고민하던 중 네이버 실시간 금융 뉴스가 있다는걸 알게 되었다. 관련주나 등락 소식을 실시간으로 띄워주니...

하지만 게으름과 귀차니즘이 발동해서 매번 들어가서 확인하기도 힘들었고 실시간으로 내가 받아볼 수는 없을까 방법을 고민했다. 그러던 중 크롤링이라는 것에 대해서 알게 되었고 이전에 마이크로 프로세서를 다룬적이 있기에 프로그램 적으로는 문제가 되지 않을 것이라고 판단, 실행에 옮기게 되었다.

 

 사실 주식 앱이라던지 관련 정보를 볼 수 있는 앱은 찾아보면 많이 나올텐데 아무래도 따로 어플을 설치할 것까지는 아닌 것 같고.. 더군다나 나는 삽질을 좋아한다.

 

 서론이 길었다. 

필요한건 이미 메세지를 보냈는지를 판단하기 위한 DB 정도만 있으면 된다.

여기서 사용할 DB는 MySQL이다.

 

 

VS Code 설치

나는 코딩할때 VS Code를 사용한다. VS Code는 아래 링크에서 받아서 설치하면 된다.

https://code.visualstudio.com/ 

 

Visual Studio Code - Code Editing. Redefined

Visual Studio Code is a code editor redefined and optimized for building and debugging modern web and cloud applications.  Visual Studio Code is free and available on your favorite platform - Linux, macOS, and Windows.

code.visualstudio.com

설치 후에 Python extension을 설치해준다.

파이썬 디버깅을 할 수 있게 해주는 Extension이다.

 

내가 크롤링 할 페이지는 아래와 같다.

https://finance.naver.com/news/news_list.nhn?mode=LSS2D§ion_id=101§ion_id2=258

 

실시간 속보 : 네이버 금융

관심종목의 실시간 주가를 가장 빠르게 확인하는 곳

finance.naver.com

실시간 뉴스를 받아서 텔레그램 봇으로 전송해 줄 예정

 

 

Telegram Bot 생성 및 설정

먼저 텔레그램 봇을 생성해줘야 한다.

텔레그램 검색창에 BotFather를 검색하고 /start 명령어를 날려준다.

 

여러 명령어가 뜨는데, 새로운 봇을 생성하려고 하니 /newbot 명령어를 입력해준다.

이름을 입력을 두번 입력해야 하는데, 첫번째로 입력하는 이름은 보여지는 이름, 두번째로 입력하는 이름은 ID이다.

이렇게 입력을 해주면 토큰값을 주는데 이걸 따로 메모를 해두면 좋다.

 

이제 이 토큰값으로 메세지를 보내 볼 것이다.

먼저 VS Code에 아래와 같이 타이핑해준다.

import telepot
from telepot.loop import MessageLoop

def Chatbot(msg):
    content_type, chat_type, chat_id, msg_date, msg_id = telepot.glance(msg, long=True)
    print(msg)
    
def main():
    MessageLoop(bot, Chatbot).run_forever()
    
if __name__ == '__main__':
    main()

그리고 챗봇을 검색해서 찾은 후, 위 코드를 실행시킨 후에 메세지를 보내보면 아래와 같은 문구가 터미널에 나타난다.

여기서 id값만 기억해두면 된다.

 

여기서 이제 메세지를 보내면 답장을 하게끔 해보겠다.

위의 코드에서 한 줄만 추가시켜주면 된다.

import telepot
from telepot.loop import MessageLoop

def Chatbot(msg):
    content_type, chat_type, chat_id, msg_date, msg_id = telepot.glance(msg, long=True)
    print(msg)
    bot.sendMessage(chat_id, msg['text'])
    
def main():
    MessageLoop(bot, Chatbot).run_forever()
    
if __name__ == '__main__':
    main()

이렇게 해준 뒤 텔레그램에서 봇에 메세지를 보내면 아래와 같이 응답한다.

그럼 텔레그램 봇 쪽의 준비도 끝났다.

이제 크롤링을 해볼 차례다.

 

크롤링

크롤링은 BeautifulSoup를 사용한다.

먼저 뉴스가 나오는 부분의 태그를 알아내야한다.

먼저 제목을 보면 li태그 밑에 dl태그, 그 밑에 클래그 명이 articleSubject인 dt및 dd 태그 안에 제목이 들어가 있는 것을 확인할 수가 있다. BS4는 Find와 Select 두가지의 방법으로 크롤링이 가능하지만 여기선 Select문을 사용하는 것으로 한다.

정리를 해보면 Select문에 들어갈 내용은 다음과 같다

title_temp = soup.select('li > dl > .articleSubject')

 

아래와 같이 프로그램 후 실행을 시켜주면 제목만 불러올 수 있게 된다.

import telepot
from telepot.loop import MessageLoop
import requests
from bs4 import BeautifulSoup

def Crawling():
    i = 19
    res = requests.get('https://finance.naver.com/news/news_list.nhn?mode=LSS2D&section_id=101&section_id2=258',
                       headers={"User-Agent": "Mozilla/5.0"})
    soup = BeautifulSoup(res.content, 'html.parser')

    title_temp = soup.select('li > dl > .articleSubject')

    while i > 0:
        print(title_temp[i].find('a')['title'])
        print("\n")
        title = title_temp[i].find('a')['title']
        SandTelegram(title)
        i = i - 1

def SandTelegram(title):
    bot = telepot.Bot("YOUR TELEGRAM BOT ID")
    bot.sendMessage('YOUR TELEGRAM ID', title)

def main():
    Crawling()


if __name__ == '__main__':
    main()

위의 코드를 응용하여 제목에 해당 기사의 링크를 하이퍼링크 설정하여 제목을 클릭하면 기사가 열리게끔 해주고, 

기사의 내용을 대략적으로 알 수 있게 기사 내용과 기사를 쓴 언론사 및 기사가 작성된 시간을 같이 보내주게끔 했다.

 

import telepot
from telepot.loop import MessageLoop
import requests
from bs4 import BeautifulSoup

def Crawling():
    i = 19
    res = requests.get('https://finance.naver.com/news/news_list.nhn?mode=LSS2D&section_id=101&section_id2=258',
                       headers={"User-Agent": "Mozilla/5.0"})
    soup = BeautifulSoup(res.content, 'html.parser')

    title_temp = soup.select('li > dl > .articleSubject')
    content_temp = soup.select('li > dl > .articleSummary')
    press_temp = soup.select('li > dl > .articleSummary > span.press')
    time_temp = soup.select('li > dl > .articleSummary > span.wdate')

    while i > 0:
        print(title_temp[i].find('a')['title'])
        print("https://finance.naver.com" + title_temp[i].find('a')['href'])
        print(content_temp[i].get_text().lstrip().split('...')[0] + '...')
        print(press_temp[i].get_text())
        print(time_temp[i].get_text())
        print("\n")
        title = title_temp[i].find('a')['title']
        href = "https://finance.naver.com" + title_temp[i].find('a')['href']
        content = content_temp[i].get_text().lstrip().split('...')[0] + '...'
        press = press_temp[i].get_text()
        time = time_temp[i].get_text()
        SandTelegram(title, href, content, press, time)
        i = i - 1

def SandTelegram(title, href, content, press, time):
    bot = telepot.Bot("YOUR TELEGRAM BOT ID")
    TeleMessage = "<a href=\"" + href + "\">" + title + "</a>" + "\n" + "\n" + content + "\n" + press + "\n" + time
    bot.sendMessage('YOUR TELEGRAM ID', TeleMessage, 'HTML')

def main():
    Crawling()


if __name__ == '__main__':
    main()

크롤링 단계는 끝났다. 

이제 DB를 활용해서 이미 보낸 데이터는 보내지 않고, 새로운 기사만 보내주는 코드를 작성해보겠다.

필자는 MySQL을 사용했다.

 

순서는 다음과 같다.

1. DB에 저장하기 전에 DB에 동일한 데이터가 있는지 확인을 하고 

2. 동일한 데이터가 있으면 데이터를 버림

3. 새로운 데이터라면 저장 후 텔레그램으로 메세지를 보냄

 

MySQL은 pymysql을 import하여 사용할 수 있다.

코드는 아래와 같다.

from Chatbot_Setting import *
import telepot
import requests
import pymysql
from bs4 import BeautifulSoup


def Crawling():
    i = 19

    res = requests.get('https://finance.naver.com/news/news_list.nhn?mode=LSS2D&section_id=101&section_id2=258',
                       headers={"User-Agent": "Mozilla/5.0"})

    soup = BeautifulSoup(res.content, 'html.parser')

    title_temp = soup.select('li > dl > .articleSubject')
    content_temp = soup.select('li > dl > .articleSummary')
    press_temp = soup.select('li > dl > .articleSummary > span.press')
    time_temp = soup.select('li > dl > .articleSummary > span.wdate')

    while i > 0:
        print(title_temp[i].find('a')['title'])
        print("https://finance.naver.com" + title_temp[i].find('a')['href'])
        print(content_temp[i].get_text().lstrip().split('...')[0] + '...')
        print(press_temp[i].get_text())
        print(time_temp[i].get_text())
        print("\n")
        title = title_temp[i].find('a')['title']
        href = "https://finance.naver.com" + title_temp[i].find('a')['href']
        content = content_temp[i].get_text().lstrip().split('...')[0] + '...'
        press = press_temp[i].get_text()
        time = time_temp[i].get_text()
        SaveDB(title, href, content, press, time)
        i = i - 1


def SaveDB(title, href, content, press, time):
    conn = pymysql.connect(host='localhost', user=DBID, password=DBPassword,
                           db='naver_finance', charset='utf8')
    curs = conn.cursor()

    sql = "select count(Title) from article where Title = %s"
    curs.execute(sql, title)
    rows = curs.fetchall()
    row = rows[0][0]

    if 0 == row :
        sql = "insert into article(Title,href,Content,Press,Time) values(%s, %s, %s, %s, %s)"
        curs.execute(sql, (title, href, content, press, time))
        conn.commit()
        SandTelegram(title, href, content, press, time)
    else:
        print("Already save to DB")

    conn.close()


def SandTelegram(title, href, content, press, time):
    bot = telepot.Bot("YOUR TELEGRAM BOT ID")
    TeleMessage = "<a href=\"" + href + "\">" + title + "</a>" + "\n" + "\n" + content + "\n" + press + "\n" + time
    bot.sendMessage('YOUR TELEGRAM ID', TeleMessage, 'HTML')

def main():
    Crawling()


if __name__ == '__main__':
    main()

 이렇게 하면 크롤링한 데이터를 DB의 데이터와 비교하고, 일치하는 데이터가 있으면(여기서는 기사의 제목으로 비교를 한다.) 콘솔에서 Already save to DB라는 문구 출력 후 다음 데이터를 비교한다. 만약 일치하는 데이터가 없을 경우 DB에 저장하고 텔레그램 메세지를 보낸 후 다음 데이터를 비교한다.

 

 MySQL 테이블은 다음과 같이 만들어줬다.

phpMyAdmin

 

 지금까지는 특정 사람에게만 데이터를 보내는 걸 코딩했다.

그럴리는 없겠지만 만약 이걸 쓰려는 사람이 많아진다면 사람이 추가될때마다 내가 일일히 코딩을 수정해가면서 ID값을 추가해야 한다.

그래서 사용자와 상호작용이 가능하도록 응답이 가능한 코드를 하나 더 작성하기로 했다.

from Chatbot_Setting import *
import telepot
from telepot.loop import MessageLoop
import requests
import pymysql
from bs4 import BeautifulSoup
bot = telepot.Bot(TokenN)


def Chatbot(msg):
    content_type, chat_type, chat_id, msg_date, msg_id = telepot.glance(msg, long=True)
    print(content_type, chat_type, chat_id, msg_date, msg_id)
    print(msg)
    conn = pymysql.connect(host='localhost', user=DBID, password=DBPassword,
                           db='naver_finance', charset='utf8')
    curs = conn.cursor()
    if content_type == 'text':

        sql = "select count(userID) from user where userID = %s"
        curs.execute(sql, chat_id)
        userIDc = curs.fetchall()
        userID = userIDc[0][0]
        if msg['text'] == '등록':
            if 0 == userID:
                sql = "insert into user(userID, Permission, Subscribe) values(%s, %s ,%s)"
                curs.execute(sql, (chat_id, 0, 1))
                conn.commit()
                print("Save DataBase")
                bot.sendMessage(chat_id, "Welcome " + msg['chat']['last_name'] + " " + msg['chat']['first_name'])
            else:
                print("Already save to DB")
                bot.sendMessage(chat_id, "Already registered.\n" + msg['chat']['last_name'] + " " + msg['chat']['first_name'])
        if 1 == userID:
            if msg["text"] == '구독':
                sql = "select Subscribe from user where userID = %s"
                curs.execute(sql,  chat_id)
                Subscribes = curs.fetchall()
                Subscribe = Subscribes[0][0]
                if 0 == Subscribe:
                    sql = "update user set Subscribe = %s where userID = %s"
                    curs.execute(sql, (1,chat_id))
                    conn.commit()
                    print("Subscribe Success")
                    bot.sendMessage(chat_id, "Subscribe Success " + msg['chat']['last_name'] + " " + msg['chat']['first_name'])
                else:
                    print("Already Subscribe")
                    bot.sendMessage(chat_id, "Already Subscribe.\n" + msg['chat']['last_name'] + " " + msg['chat']['first_name'])
            elif msg['text'] == '구독취소':
                sql = "select Subscribe from user where userID = %s"
                curs.execute(sql,  chat_id)
                Subscribes = curs.fetchall()
                Subscribe = Subscribes[0][0]
                if 0 == Subscribe:
                    print("Already UnSubscribe")
                    bot.sendMessage(chat_id, "Already UnSubscribe.\n" + msg['chat']['last_name'] + " " + msg['chat']['first_name'])
                else:
                    sql = "update user set Subscribe = %s where userID = %s"
                    curs.execute(sql, (0,chat_id))
                    conn.commit()
                    print("UnSubscribe Success")
                    bot.sendMessage(chat_id, "UnSubscribe Success " + msg['chat']['last_name'] + " " + msg['chat']['first_name'])

def main():
    MessageLoop(bot, Chatbot).run_forever()


if __name__ == '__main__':
    main()

 최초 1 회 등록을 하고, 구독이라고 하면 현재 대화하고 있는 사용자의 Chat ID가 DB에 저장되어 다음 메세지를 보낼때 쓸 수 있게 했다. 이렇게 하면 Chat ID를 일일히 찾아서 하드코딩 할 필요가 없어진다.

당연히 이전에 작성한 프로그램도 수정을 해줘야한다.

 

SandTelegram 함수를 아래와 같이 수정했다.

def SandTelegram(title, href, content, press, time):
    conn = pymysql.connect(host='localhost', user=DBID, password=DBPassword,
                           db='naver_finance', charset='utf8')
    curs = conn.cursor()

    sql = "select userID from user"
    curs.execute(sql)
    rows = curs.fetchall()
    for row in rows:
        userID = row[0]
        sql = "select Subscribe from user where userID = %s"
        # temp = int(userID)
        curs.execute(sql, userID)
        Sub = curs.fetchall()
        if Sub[0][0] == 1:
            bot = telepot.Bot(TokenN)
            TeleMessage = "<a href=\"" + href + "\">" + title + "</a>" + "\n" + "\n" + content + "\n" + press + "\n" + time
            bot.sendMessage(int(userID), TeleMessage, 'HTML')

앞서 작성한 프로그램을 테스트 해봤다.

 

나는 이미 DB에 등록이 되어있는 상태여서 이미 구독을 했다고 출력이 된다.

이제 불특정 다수가 내 챗봇 메세지를 받아볼 수 있게 되었다!

 

다음에는 위 프로그램을 활용하여 할인 정보를 긁어오는 프로그램을 작성해보고자 한다.