'한라대학교 공지 알림 봇 제작기' 시리즈한라대학교 공지 알림 봇 제작기 (8) - 유지보수 2탄

김학진

@mildsalmon

흔치않고, 진귀하다.

Intro

다시는 이 시리즈에 글 쓸 일이 없길 바랐지만.

짜잔.
그럴리가 없죠.

시작하겠습니다.
유지보수 2탄

사건 설명

사건 개요

알람이 폭팔적으로 와서,
'또 제목을 바꾼건가?', '난 잘 고쳤는데?'라는 생각을 했다.
그런데 그게 아니였다.

게시글을 지운것이다.

게시글은 3가지 방식으로 작동된다.
작성,수정,삭제
작성수정에 대한 대책은 바로 앞 게시글에서 해결했다.
하지만 삭제를 할 줄은...
상상도 하지 못했다.

어쨋든 유지보수를 시작하자.

수정 과정

게시글 삭제에 대처하기 위해서는 크롤링한 데이터에 대한 의존성이 높아지게된다.
구상을 이렇게 했다.
크롤링한 데이터를 좀 더 활용해보자.
각 게시글의 제목은 리스트 형식으로 데이터가 정제된다.
정제된 데이터인<a>태그는 각 태그마다 고유한 값(boardSeq)이 존재한다.
그럼 이 고유한 값만 모아서 리스트 형태로 저장시킨 후, 로컬에 저장한다.
그리고 최신 게시글을 판단하는 부분에서 로컬의 값 3~5개 정도와 크롤링한 데이터를 비교하는 보조 검사대를 만들자.

대충 이런 모습이다.

자 그럼 만들자.

리스트가 2개 필요하다.
local에 파일로 저장할 리스트, 코드를 실행하면서 사용할(그림에서 웹 데이터) 리스트.

for post in posts:
    save_boardSeq.append(str(post).split("&amp")[2])  # 최신 공지랑 같은지 검사
    use_boardSeq.append(str(post).split("&amp;")[2])

save_boardSeq[0] = save_boardSeq[0].lstrip(";")

기존에는 boardSeq를 가장 최신 게시물 한개만 저장했다.
변경 후에는 크롤링한 모든 게시글의 boardSeq를 리스트로 저장한다.
save_boardSeq에서 맨 왼쪽';'를 제거하는 이유는 나중에 데이터를 불러올 때 ;를 기준으로 리스트를 만들 것이다.
그런데 맨 왼쪽에 ';'이 있으면 리스트를 만들 때 ['', 'boardSeq=~', 'boardSeq=~'] 이런식으로 나온다.
즉, 공백 원소를 제거하기 위함이다.
save_boardSeq만으로 데이터를 검사하려고 하니 많이 불편했다.
그래서 하나 더 만들고 '&amp;'로 ;까지 다 날려서 순수한 boardSeq를 만들었다.

if not (os.path.isfile(os.path.join(BASE_DIR, 'latest.txt'))):
    new_file = open("latest.txt", 'w+', encoding='utf-8')
    new_file.writelines(save_boardSeq)
    new_file.close()

writelines는 리스트 형태로도 파일에 저장이 가능하다.
writeline은 불가능.

    before = f_read.readline().split(";")
    for i, post in enumerate(posts):  # 기존 크롤링 한 부분과 최신 게시글 사이에 게시글이 존재하는지 확인
        print("post = " + post.text)
        print("new = " + use_boardSeq[i])
        print("before = " + before[0])  #
        if before[0] == use_boardSeq[i]:
            print("최신글입니다.")
            break
        elif before[1] == use_boardSeq[i]:
            print("두번째 게시글이랑 체크, 첫 게시글 삭제된거냐")
            break
        elif before[2] == use_boardSeq[i]:
            print("게시글 2개 삭제는 에바자나")
            break
        else:
            url = "http://www.halla.ac.kr/mbs/kr/jsp/board/" + post.get('href')
            print(url)
            try:
                if post != posts[5]:  # 10번 post 이상 넘어가는지 확인 / 텔레그램 메시지가 url 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")  # 짧은 시간에 message를 과도하게 보내면 timeout이 뜨는것같다.
                break  # message를 많이 보내서 발생한다기 보다는, 한번에 보낼 수 있는 url의 양이 10개로 제한되어 있는듯

before 부분에서 한 줄로 저장된 데이터를 문자열 형태로 받게된다.
받은 문자열을 ';'를 기준으로 리스트를 만들었다.
python은 for문에서 리스트를 이용하여 루프를 돌릴때 enumerate를 사용하면 인덱스를 같이 넘겨준다.

  1. 새로운 공지가 안올라온 경우
  • 첫 if문에서 참이면 for문을 벗어난다.
  1. 새로운 공지가 작성된 경우
  • if문, elif문 전부 거짓이면 else문을 통과하고 if문이랑 일치하는지 루프를 돌면서 확인한다.
    맞다면 for문을 탈출하고, 아니라면 반복한다.
  1. 이전 게시글이 삭제된 경우
  • if문을 넘어서 elif문을 만날 것이다.
    web data기준 2번 게시글의 데이터도 가지고 있어서 elif로 검증이 가능하다.
    elif문은 일부로 2개만 만들었다.
    즉 local에 2번 인덱스까지만 검사한다.
    한 번에 게시글 3개가 날라갈일은 없다고 생각하고 만들었다.
  1. 게시글이 수정되는 경우
  • 게시글 확인을 게시글 제목이 아닌, 게시글 고유 숫자로 검사하기 때문에 상관없다.

elif 안에 print를 적은 이유는 만약에 저 elif문이 실행될 경우 로그를 찍어보기 위해서 남겼다.

유지보수 결과

# -*- coding: utf-8 -*-

########## 서비스 코드

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

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')
# 일반 = boardId = 23401
client_errors = [400, 401, 403, 404, 408]
server_errors = [500, 502, 503, 504]

print(time.strftime("%c", time.localtime(time.time())))

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='공지')
save_boardSeq = []
use_boardSeq = []

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

for post in posts:
    save_boardSeq.append(str(post).split("&amp")[2])  # 가장 최신 공지랑 같은지 검사
    use_boardSeq.append(str(post).split("&amp;")[2])

save_boardSeq[0] = save_boardSeq[0].lstrip(";")

if not (os.path.isfile(os.path.join(BASE_DIR, 'latest.txt'))):
    new_file = open("latest.txt", 'w+', encoding='utf-8')
    new_file.writelines(save_boardSeq)
    new_file.close()

with open(os.path.join(BASE_DIR, 'latest.txt'), 'r+', encoding='utf-8') as f_read:  # DB 구현후 변경 에정
    before = f_read.readline().split(";")
    for i, post in enumerate(posts):  # 기존 크롤링 한 부분과 최신 게시글 사이에 게시글이 존재하는지 확인
        print("post = " + post.text)
        print("new = " + use_boardSeq[i])
        print("before = " + before[0])  #
        if before[0] == use_boardSeq[i]:
            print("최신글입니다.")
            break
        elif before[1] == use_boardSeq[i]:
            print("두번째 게시글이랑 체크, 첫 게시글 삭제된거냐")
            break
        elif before[2] == use_boardSeq[i]:
            print("게시글 2개 삭제는 에바자나")
            break
        else:
            url = "http://www.halla.ac.kr/mbs/kr/jsp/board/" + post.get('href')
            print(url)
            try:
                if post != posts[5]:  # 10번 post 이상 넘어가는지 확인 / 텔레그램 메시지가 url 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")  # 짧은 시간에 message를 과도하게 보내면 timeout이 뜨는것같다.
                break  # message를 많이 보내서 발생한다기 보다는, 한번에 보낼 수 있는 url의 양이 10개로 제한되어 있는듯

with open(os.path.join(BASE_DIR, 'latest.txt'), 'w+', encoding='utf-8') as f_write:
    f_write.writelines(save_boardSeq)

print("\n")

마치며

알람 갯수를 10개에서 5개로 조정했는데
테스트 코드쪽에만 적용이 된 모양이다.
오늘 이 사건이 발생했을때 메세지 20개 (url 10개)가 동시에 날라오는 것을 보고 참... 많은 생각이 들었다.
테스트 코드 뿐만 아니라 실 서비스 코드에서도 확인을 꼭 잘하자.
귀찮고 힘들어도 두번 세번 확인해야 한다.
안그러면 대참사가 일어날테니까...

그리고 그림판을 너무 오랜만에 사용해서 도형들이 별로 이쁘지 않다.
원래는 더 자유롭게 사용했었는데..
뭐 하다보면 나아지겠지.

'한라대학교 공지 알림 봇 제작기' 시리즈

매번 학교 홈페이지에 가서 최신 공지사항이 올라왔는지 확인하기 불편해서 나만 쓰려고 만든 토이프로젝트였으나, 나처럼 불편해할 사람들이 있으리라 생각해 배포 중인 서비스
baealex
9개월전

태스트 코드 뿐만 아니라 오타 제보합니다 🙂

mildsalmon
9개월전

@baealex 수정했습니다. 감사합니다 ㅎㅎ

댓글을 작성하기 위해 로그인이 필요합니다.