한라대학교 공지 알림 봇 제작기 (2) - 코딩

'한라대학교 공지 알림 봇 제작기' 시리즈한라대학교 공지 알림 봇 제작기 (2) - 코딩

mildsalmon

흔치않고, 진귀하다.

Sign in to view email

A. 개괄적 도식화

개괄적 도식화

개괄적으로 도식화를 하면 저런 모습이다.

나(client)는 학교 공지사항이 최신화 되면 알람받기를 원한다

위 목적을 해결하기 위해서는

학교 공지사항이 최신화 되면

알람오기를 원한다 (나에게)

위 두가지 사항을 만족시켜야 한다. 그러므로 나는 학교 공지사항이 최신화 되는지 지속적으로 확인하는 부분알람을 보내주는 부분을 만들것이다.

때문에 알람을 보내주는 부분은 텔레그램과 카카오톡을 활용하여 코딩을 하고, 그 코드는 지속적으로 학교 공지사항이 최신화 되는지 확인해줘야한다.

서버를 사용하는 이유는 나는 사정상 pc를 항상 켜둘 수 없는 상황이다. 학교 공지사항이 최신화 되는지 어플(텔레그램, 카카오톡)에서 언제나 확인하기 위해서는 pc가 꺼지지 않고 지속적으로 코드를 실행시켜야 하기 때문이다.

B. 코딩 (수정전)

import requests
from bs4 import BeautifulSoup
import os
import telegram

my_token = '****'
bot = telegram.Bot(token=my_token)
my_chat_id = "****"

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
while True:
    req = requests.get('http://www.halla.ac.kr/mbs/kr/jsp/board/list.jsp?boardId=23401&mcategoryId=&id=kr_060101000000')
    req.encoding = 'utf-8'

    html = req.text
    soup = BeautifulSoup(html, 'html.parser')
    posts = soup.select('table > tbody > tr > td > a')

    count_page_num = 0
    count_notice_num = 0

    for i in posts:
        category = i.get('href')
        count_page_num = count_page_num + 1  # 공지 제목 다음을 카운트하기 위해
        if 'mcategoryI' not in category:
            count_notice_num = count_page_num

    latest = posts[count_notice_num].text
    latest_category = posts[count_notice_num].get('href')

    with open(os.path.join(BASE_DIR, 'latest.txt'), 'r+') as f_read:
        before = f_read.readline()
        if before != latest:
            boardSeq = latest_category.find('boardSeq=')
            boardSeq_number = latest_category[boardSeq + 9:]
            url = "http://www.halla.ac.kr/mbs/kr/jsp/board/view.jsp?spage=1&boardId=23401&boardSeq=" + boardSeq_number + "&mcategoryId=&id=kr_060101000000&column=&search="
            bot.sendMessage(chat_id=my_chat_id, text="새 공지사항이 있습니다.")
            bot.sendMessage(chat_id=my_chat_id, text=latest)
            bot.sendMessage(chat_id=my_chat_id, text=url)
            with open(os.path.join(BASE_DIR, 'latest.txt'), 'w+') as f_write:
                f_write.write(latest)
                f_write.close()
        f_read.close()

전체 코드는 이렇게 된다.
위 코드는 내가 입대하기 전 작성한 코드이다.
입대하고 텔레그램 계정 자체를 삭제하다보니 봇 토큰과 chat_id가 날라가서 해당 부분만 수정했다.

sleep을 추가하여 학교 서버에 주는 부하를 줄여주는 것이 좋다._BLEX 님

a. 모듈

1. requests

Requests Document 정리본

세션부분은 미완성이다. Requests는 웹에 요청을 하면 서버에서 응답을 하는 형식이다. Requests를 통해 알 수 있는 정보는 웹 서버의 응답 코드, header, encoding, text, json 등이 있다.
주제가 웹 크롤링이다 보니까 꼭 사용해야 하는 모듈이다.

2. beautifulsoup4

BeautifulSoup4 Document 정리본

BeautifulSoup는 문서를 (분해, 분석, 검색)하는 역할을 한다. requests에서 웹를 읽어온다면 이 웹에서 나에게 필요한 정보를 뽑아내는 작업을 해야한다.

직접 손으로 골라내도 상관은 없지만, 지금 이 코드의 자동화를 위해, 좀 더 간편하게 작성하기 위해 등등의 이유로 널리 배포되어 있는 BeautifulSoup 모듈를 사용하게 되었다.

3. python-telegram-bot

python-telegram-bot 정리본

이건 텔레그램 봇을 컨트롤하기 위해서 필요한 부분이다.

4. os

os 정리본

OS 모듈은 환경 변수나 디렉토리, 파일 등의 OS 자원을 제어할 수 있게 해주는 모듈이다.

C. 코딩 (수정후)

import requests
from bs4 import BeautifulSoup
import os
import telegram
import sys

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

my_token = '****'
req = requests.get('http://www.halla.ac.kr/mbs/kr/jsp/board/list.jsp?boardId=23401&mcategoryId=&id=kr_060101000000')
my_chat_id = "****"

client_errors = [400, 401, 403, 404, 408]
server_errors = [500,502, 503, 504]

if req.status_code in client_errors:
    sys.exit(1)
elif req.status_code in server_errors:
    sys.exit(1)

bot = telegram.Bot(token=my_token)
html = req.text
soup = BeautifulSoup(html, 'html.parser')
posts = soup.select('table > tbody > tr > td > a')
count_page_num = 0
count_notice_num = 0

for post in posts:
    post_href = post.get('href')
    if 'mcategoryId' in post_href:
        count_notice_num = count_page_num
        break
    count_page_num = count_page_num + 1

for i in range(count_notice_num):           # 공지로 위로 올라간 게시글 제외한 최신 게시글 분류
    del posts[0]


with open(os.path.join(BASE_DIR, 'latest.txt'), 'r+') as f_read:    # DB 구현후 변경 에정
    before = f_read.readline()

    for post in posts:                      # 기존 크롤링 한 부분과 최신 게시글 사이에 게시글이 존재하는지 확인
        if before == post.text:
            print("최신글입니다.")
            break
        elif before != post:
            url = "http://www.halla.ac.kr/mbs/kr/jsp/board/" + post.get('href')
            print(url)
            bot.sendMessage(chat_id=my_chat_id, text="새 공지사항이 있습니다.")
            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+') as f_write:
                f_write.write(post.text)
                # f_write.close()
    # f_read.close()

수정 이유를 간단히 설명하면 웹 페이지를 requests했을때 자주 발생하는 HTML 응답코드(클라이언트 에러, 서버 에러)에 대한 처리를 추가. (예외 처리로 변경 예정) 사용자가 수정해야 하는 부분(my_token, my_chat_id)을 맨 위로 올려서 편리하게 수정 할 수 있도록 함. 이 halla_notice_alarm.py는 서버에 올라간다면 1시간 마다 실행될 예정. 즉 주기적 패턴을 가지고 실행하는 것이 while문을 써서 계속 서버에서 작동하게 하는 것보다 서버 측 부하를 줄여준다고 생각해서 while문을 빼고 서버(리눅스)에서 cron을 통해 작업 예약 설정을 할것임.

코드 분석은 다음 포스트부터 다루겠습니다.

참고자료

나만의 웹 크롤러 만들기, "https://beomi.github.io/gb-crawling/", [Beomi's tech blog]

python으로 telegram bot 활용하기 - 1 기본 설정편, "https://blog.psangwoo.com/coding/2016/12/08/python-telegram-bot-1.html", [Bill-Park]

[Python] BeautifulSoup4 라이브러리와 기초 (멜론차트 크롤링), "https://rednooby.tistory.com/98" [개발자의 취미생활]

Telegram bot 개발, "https://idlecomputer.tistory.com/122", [백곳]

beautifulsoup4, "https://blex.me/@mildsalmon/beautifulsoup", [mildsalmon]

Requests, "https://blex.me/@mildsalmon/requests", [mildsalmon]

python Telegram Bot, "https://blex.me/@mildsalmon/telegram", [mildsalmon]

OS, "https://blex.me/@mildsalmon/os", [mildsalmon]

notice_alarm, "https://github.com/mildsalmon/notice_alarm", [mildsalmon]

mildsalmon
4개월전

@bbapico980728 my_token과 my_chat_id는 텔레그램 봇을 사용할때 입력해줘야하는 값입니다. 토큰은 봇을 생성하면 주어지는 값이구여 (아마 다음 게시글에 있을꺼에요 !) chat_id는 밑에 send_message 부분에 chat_id를 수정하기 편하게 코드 맨 위에 작성한겁니다 !. chat_id는 봇이 메세지를 보낼 채팅방의 주소라고 생각하시면 됩니다

로그인된 사용자만 댓글을 작성할 수 있습니다.