# [한라대학교 공지 알림 봇] 코드분석 (telegram bot)

- Author: @mildsalmon
- Published: 2020-02-24
- Updated: 2021-09-14
- Source: http://blex.me/@mildsalmon/%ED%95%9C%EB%9D%BC%EB%8C%80%ED%95%99%EA%B5%90-%EA%B3%B5%EC%A7%80-%EC%95%8C%EB%A6%BC-%EB%B4%87-%EC%A0%9C%EC%9E%91%EA%B8%B0-3-%EC%BD%94%EB%93%9C%EB%B6%84%EC%84%9D-telegrambot
- Tags: python, 텔레그램봇, 한라대학교, 웹크롤링, 공지알림봇, 코드분석

---

# Intro

이번 챕터에서는 telegram bot을 활용해서 공지가 생기면 알람이 오도록 구현해보겠다.

![지금까지 만든 결과](https://static.blex.me/images/content/2020/2/25/VEAPjOljAxmbdaCefQWT.png "지금까지 만든 결과")

지금까지 만든 코드를 실행하면 위 같은 결과가 나온다.<br>
공지를 제외한 1page에 있는 게시글을 전부 크롤링한 모습이다.<br>


# telegram bot

[텔레그램 봇 정리본](https://blex.me/@mildsalmon/telegram)<br>
위 정리본에 링크 되어 있는 문서를 참고하자.

### 봇 만들기

##### BotFather

텔레그램 봇은 BotFather을 통해 만들 수 있다.

![](https://static.blex.me/images/content/2020/2/25/wlMHnbQSp4WFrtgctVVd.png)

![](https://static.blex.me/images/content/2020/2/25/bT7krpAbHlXjIjVqY9dm.png)

![](https://static.blex.me/images/content/2020/2/25/9LB4NGuVIAfivrr5R3jd.png)

![](https://static.blex.me/images/content/2020/2/25/ugtZubM09fHPDwxJEda4.png)

만드는 방법은 스크린샷을 보고 따라하면 된다.<br>
`/start` 메세지를 보내고<br>
`/newbot` 메세지를 보내고<br>
`봇 이름`을 적어주고<br>
`봇 사용자 이름`을 적어주면 된다.<br>
그러면 `봇 주소`와 `토큰`이 발행된다.<br>
봇 주소로 들어가면 방금 만든 봇이 나타난다.<br>

### 채널 만들기

봇을 이용하지 않고 채널을 통해 만든 이유는 봇은 사용자가 메세지를 보내면 응답을 해주는 방식이다.<br>
하지만 채널은 새로운 공지가 있을경우 봇이 채널에 메세지를 보내는 방식이다.<br>
`사용자가 메세지를 보내야 한다는 조건이 붙지 않아서 채널을 이용했다.`<br>
그리고 채널은 `관리자를 제외한 일반 유저들은 채팅을 치지 못한다.`<br>
그래서 공지 알림만 오게 하는 용도로 적합하다고 생각했다.<br>

##### 채널 개설 방법

![](https://static.blex.me/images/content/2020/2/25/hma1VDy66Uhzbx7dgF8T.png)

![](https://static.blex.me/images/content/2020/2/25/sBOUgxo807oSq9FM26ju.png)

![](https://static.blex.me/images/content/2020/2/25/F5U1nr8XhsOxEcEWBcG7.png)

![](https://static.blex.me/images/content/2020/2/25/Vm4j1b4f8nYzFyUVTjDo.png)

![](https://static.blex.me/images/content/2020/2/25/Dy8zfvz8QWTgiWf0pCtE.png)

![](https://static.blex.me/images/content/2020/2/25/ra7TaW7hW8usEDYVKLrS.png)

![](https://static.blex.me/images/content/2020/2/25/0iPtAJdvfvrWqtWQwbUI.png)

![](https://static.blex.me/images/content/2020/2/25/Vd7WmdCYnrTBauTv8OfP.png)

![](https://static.blex.me/images/content/2020/2/25/Xwbwqe41Rd00hazTry6H.png)

![](https://static.blex.me/images/content/2020/2/25/HTftpfudOy6EyWpXb6OT.png)

스크린샷을 보고 잘 따라오면 채널을 쉽게 만들 수 있다.<br>
중간에 채널 주소는 꼭 기억해야한다.<br>

### 테스트 채널 만들기

실 서비스 채널이 있으면, 테스트 채널도 있어야 한다.<br>

##### 테스트 채널 개설 방법

테스트 채널은 위 채널 만드는 방식이랑 똑같이 만든 다음에<br>

[https://api.telegram.org/bot1081363002:AAGpV0UOaOLu7nbY-M4sTHCav6YIDoorHKQ/sendMessage?chat_id=@hallatest&text=h](https://api.telegram.org/bot1081363002:AAGpV0UOaOLu7nbY-M4sTHCav6YIDoorHKQ/sendMessage?chat_id=@hallatest&text=h)

> [https://api.telegram.org/bot(봇 토큰)/sendMessage?chat_id=@(채널 링크)&text=hi](1 "https://api.telegram.org/bot(봇 토큰)/sendMessage?chat_id=@(채널 링크)&text=hi")

![](https://static.blex.me/images/content/2020/2/25/dhHTgQaYlmUstr2iQQAe.png)

위 주소로 들어가서 `chat id`를 확인한다.<br>
그리고 설정에 들어가서 채널을 공개 채널에서 `비공개 채널`로 바꿔주면 된다.<br>

### 코딩

```python
import requests
from bs4 import BeautifulSoup
import sys
import telegram



my_token = '봇 토큰'
my_chat_id = '채널 주소 / 서비스 채널은 @*** / 테스트 체널은 -***'
req = requests.get('http://www.halla.ac.kr/mbs/kr/jsp/board/list.jsp?boardId=23401&mcategoryId=&id=kr_060101000000')

client_errors = [400, 401, 403, 404, 408]
server_errors = [500,502, 503, 504]

if req.status_code in client_errors:
    print(req.status_code + ": 클라이언트 에러")
    sys.exit(1)
elif req.status_code in server_errors:
    print(req.status_code + ": 서버 에러")
    sys.exit(1)

bot = telegram.Bot(token=my_token)
html = req.text
soup = BeautifulSoup(html, "html.parser")
posts = soup.select("td > a")
num = soup.find_all(title='공지')

for i in range(len(num)):           # 공지로 위로 올라간 게시글 제외한 최신 게시글 분류
    del posts[0]

bot.sendMessage(chat_id=my_chat_id, text='일반공지 : ' + posts[0].text)
```

코드를 실행시키면 텔레그램 채널로 봇이 메세지를 보낸다.<br>
그러면 봇 구현은 완성이다.<br>
남은 건 최신 게시글인지 확인 후 최신 게시글이면 봇을 통해 메세지를 보내서 알리고, 아니면 메세지를 보내지 말아야한다.<br>

# open

[os 정리본](https://blex.me/@mildsalmon/os)

이제 우린 어떤 글이 최신 글인지 확인하는 방법을 생각해야한다.

1. 공지 알림 봇은 기본적으로 텔레그램 채널에서 실행된다.
2. 코드는 서버에서 1시간마다 실행된다.

그러면 우리가 말하는 최신 글은, 글 옆에 주황색 N 박스가 아닌<br>
`서버에서 코드를 실행하는 주기(1시간) 안에 게시되는 모든 글이다.`<br>
다르게 말하자면, 텔레그램 채널에 봇이 알리지 못한 게시글을 뜻한다.<br>

```python
import requests
from bs4 import BeautifulSoup
import sys
import telegram
import os

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

my_token = '봇 토큰'
my_chat_id = '채널 주소 / 서비스 채널은 @*** / 테스트 체널은 -***'
req = requests.get('http://www.halla.ac.kr/mbs/kr/jsp/board/list.jsp?boardId=23401&mcategoryId=&id=kr_060101000000')

client_errors = [400, 401, 403, 404, 408]
server_errors = [500,502, 503, 504]

if req.status_code in client_errors:
    print(req.status_code + ": 클라이언트 에러")
    sys.exit(1)
elif req.status_code in server_errors:
    print(req.status_code + ": 서버 에러")
    sys.exit(1)

bot = telegram.Bot(token=my_token)
html = req.text
soup = BeautifulSoup(html, "html.parser")
posts = soup.select("td > a")
num = soup.find_all(title='공지')

for i in range(len(num)):           # 공지로 위로 올라간 게시글 제외한 최신 게시글 분류
    del posts[0]

if not(os.path.isfile(os.path.join(BASE_DIR, 'latest.txt'))):
    new_file = open('latest.txt', 'w+', encoding='utf-8')
    new_file.write(posts[0].text)
    new_file.close()

with open(os.path.join(BASE_DIR, 'latest.txt'), 'r+', encoding='utf-8') as f_read:
    before = f_read.readline()

    for post in posts:
        print('post = ' + post.text)
        print('before = ' + before)
        if before == post.text:
            print('최신글입니다.')
            break
        elif before != post.text:
            url = "http://www.halla.ac.kr/mbs/kr/jsp/board/" + post.get('href')
            print(url)
            bot.sendMessage(chat_id=my_chat_id, text="일반공지 : " + post.text)
            bot.sendMessage(chat_id=my_chat_id, text=url)

with open(os.path.join(BASE_DIR, 'latest.txt'), 'w+', encoding='utf--8') as f_write:
    f_write.write(posts[0].text)
```

가장 최근에 크롤링한 `게시글.text`를 txt파일에 저장하기로 결정했다.

1. latest.txt 파일이 있는 경우
2. latest.txt 파일이 없는 경우

두 경우가 나온다.<br>
파일이 없을 경우 새로 만들어 줘야 한다.<br>

```python
if not(os.path.isfile(os.path.join(BASE_DIR, 'latest.txt'))):
    new_file = open('latest.txt', 'w+', encoding='utf-8')
    new_file.write(posts[0].text)
    new_file.close()
```

위 코드 처럼 파일을 새로 만든다음에 닫아주자

파일이 있는 경우는 파일을 열어서 저장된 내용을 변수에 저장한다.<br>
파일의 내용은 `게시글.text`이기 때문에 크롤링한 게시글과 쉽게 비교할 수 있다.<br>
크롤링한 데이터와 파일에 저장된 데이터를 비교하다가 게시글이 일치하면 더 비교할 필요 없이 종료하면 된다.<br>
데이터가 일치하지 않을 경우는 bot을 통해 크롤링한 테이더를 메세지로 보내면 된다.<br>

```python
    for post in posts:
        print('post = ' + post.text)
        print('before = ' + before)
        if before == post.text:
            print('최신글입니다.')
            break
        elif before != post.text:
            url = "http://www.halla.ac.kr/mbs/kr/jsp/board/" + post.get('href')
            print(url)
            bot.sendMessage(chat_id=my_chat_id, text="일반공지 : " + post.text)
            bot.sendMessage(chat_id=my_chat_id, text=url)
```

마지막으로 코드를 한번 실행 했으므로 latest.txt 파일에는 최신 게시글(크롤링한 게시글 중 최신)이 들어가야 한다.<br>
고로 마지막에 따로 저장해주자.<br>

```python
with open(os.path.join(BASE_DIR, 'latest.txt'), 'w+', encoding='utf--8') as f_write:
    f_write.write(posts[0].text)
```

# 에러처리

테스트를 여러번 해본 결과 텔레그램 `봇은 url을 메세지로 보낼때 한번에 최대로 보낼 수 있는 양`이 정해져 있는 듯하다.<br>
`한번에 최대 10개`까지만 보낼 수 있다.<br>
그 이상 보내면 코드의 속도도 느려질 뿐 아니라 timeout에러 메세지를 나타내면서 코드가 강제로 꺼진다.<br>

```python
            try:
                if post != posts[10]:
                    bot.sendMessage(chat_id=my_chat_id, text="일반공지 : " + post.text)
                    bot.sendMessage(chat_id=my_chat_id, text=url)
                else:
                    break
            except Exception as ex:
                print("timeout")
                break
```

따라서 위 예외 처리를 `bot.sendMessage` 부분에 넣어주면 된다.<br>
어차피 1시간 간격으로 코드가 실행되기 때문에 최신 게시글이 10개 이상 나올 확률은 적지만, 가능성이 적다고해서 발생 가능한 에러를 무시하고 그냥 지나치는건.. 좀 곤란하다.<br>

# 최종 코드

```python
import requests
from bs4 import BeautifulSoup
import sys
import telegram
import os

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

my_token = '봇 토큰'
my_chat_id = '채널 주소 / 서비스 채널은 @*** / 테스트 체널은 -***'
req = requests.get('http://www.halla.ac.kr/mbs/kr/jsp/board/list.jsp?boardId=23401&mcategoryId=&id=kr_060101000000')

client_errors = [400, 401, 403, 404, 408]
server_errors = [500,502, 503, 504]

if req.status_code in client_errors:
    print(req.status_code + ": 클라이언트 에러")
    sys.exit(1)
elif req.status_code in server_errors:
    print(req.status_code + ": 서버 에러")
    sys.exit(1)

bot = telegram.Bot(token=my_token)
html = req.text
soup = BeautifulSoup(html, "html.parser")
posts = soup.select("td > a")
num = soup.find_all(title='공지')

for i in range(len(num)):           # 공지로 위로 올라간 게시글 제외한 최신 게시글 분류
    del posts[0]

if not(os.path.isfile(os.path.join(BASE_DIR, 'latest.txt'))):
    new_file = open('latest.txt', 'w+', encoding='utf-8')
    new_file.write(posts[0].text)
    new_file.close()

with open(os.path.join(BASE_DIR, 'latest.txt'), 'r+', encoding='utf-8') as f_read:
    before = f_read.readline()

    for post in posts:
        print('post = ' + post.text)
        print('before = ' + before)
        if before == post.text:
            print('최신글입니다.')
            break
        elif before != post.text:
            url = "http://www.halla.ac.kr/mbs/kr/jsp/board/" + post.get('href')
            print(url)
            try:
                if post != posts[10]:
                    bot.sendMessage(chat_id=my_chat_id, text="일반공지 : " + post.text)
                    bot.sendMessage(chat_id=my_chat_id, text=url)
                else:
                    break
            except Exception as ex:
                print("timeout")
                break

with open(os.path.join(BASE_DIR, 'latest.txt'), 'w+', encoding='utf--8') as f_write:
    f_write.write(posts[0].text)
```

# 다음시간에

다음 챕터에서는 서버에 코드를 올려서 자동으로 실행시키는 스케쥴링에 관해 다룰것이다.

# 참고문헌

> Python Telegram Bot document, "https://blex.me/@mildsalmon/telegram", [mildsalmon]
>
> OS document, "https://blex.me/@mildsalmon/os", [mildsalmon]
>
> notice_alarm, "https://github.com/mildsalmon/notice_alarm", [mildsalmon]
