Intro
다시는 이 시리즈에 글 쓸 일이 없길 바랐지만.
짜잔.
그럴리가 없죠.
시작하겠습니다.
유지보수 2탄
사건 설명
사건 개요
알람이 폭팔적으로 와서,
'또 제목을 바꾼건가?', '난 잘 고쳤는데?'라는 생각을 했다.
그런데 그게 아니였다.
게시글을 지운것이다.
게시글은 3가지 방식으로 작동된다.
작성
,수정
,삭제
작성
과 수정
에 대한 대책은 바로 앞 게시글에서 해결했다.
하지만 삭제
를 할 줄은...
상상도 하지 못했다.
어쨋든 유지보수를 시작하자.
수정 과정
게시글 삭제에 대처하기 위해서는 크롤링한 데이터에 대한 의존성이 높아지게된다.
구상을 이렇게 했다.
크롤링한 데이터를 좀 더 활용해보자.
각 게시글의 제목은 리스트 형식으로 데이터가 정제된다.
정제된 데이터인<a>
태그는 각 태그마다 고유한 값(boardSeq)이 존재한다.
그럼 이 고유한 값만 모아서 리스트 형태로 저장시킨 후, 로컬에 저장한다.
그리고 최신 게시글을 판단하는 부분에서 로컬의 값 3~5개 정도와 크롤링한 데이터를 비교하는 보조 검사대를 만들자.
대충 이런 모습이다.
자 그럼 만들자.
리스트가 2개 필요하다.
local에 파일로 저장할 리스트, 코드를 실행하면서 사용할(그림에서 웹 데이터) 리스트.
for post in posts:
save_boardSeq.append(str(post).split("&")[2]) # 최신 공지랑 같은지 검사
use_boardSeq.append(str(post).split("&")[2])
save_boardSeq[0] = save_boardSeq[0].lstrip(";")
기존에는 boardSeq를 가장 최신 게시물 한개만 저장했다.
변경 후에는 크롤링한 모든 게시글의 boardSeq를 리스트로 저장한다.
save_boardSeq
에서 맨 왼쪽';'를 제거하는 이유는 나중에 데이터를 불러올 때 ;
를 기준으로 리스트를 만들 것이다.
그런데 맨 왼쪽에 ';'이 있으면 리스트를 만들 때 ['', 'boardSeq=~', 'boardSeq=~'] 이런식으로 나온다.
즉, 공백 원소를 제거하기 위함이다.
save_boardSeq
만으로 데이터를 검사하려고 하니 많이 불편했다.
그래서 하나 더 만들고 '&'로 ;까지 다 날려서 순수한 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
를 사용하면 인덱스를 같이 넘겨준다.
- 새로운 공지가 안올라온 경우
- 첫 if문에서 참이면 for문을 벗어난다.
- 새로운 공지가 작성된 경우
- if문, elif문 전부 거짓이면 else문을 통과하고 if문이랑 일치하는지 루프를 돌면서 확인한다.
맞다면 for문을 탈출하고, 아니라면 반복한다.
- 이전 게시글이 삭제된 경우
- if문을 넘어서 elif문을 만날 것이다.
web data기준 2번 게시글의 데이터도 가지고 있어서 elif로 검증이 가능하다.
elif문은 일부로 2개만 만들었다.
즉 local에 2번 인덱스까지만 검사한다.
한 번에 게시글 3개가 날라갈일은 없다고 생각하고 만들었다.
- 게시글이 수정되는 경우
- 게시글 확인을 게시글 제목이 아닌, 게시글 고유 숫자로 검사하기 때문에 상관없다.
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("&")[2]) # 가장 최신 공지랑 같은지 검사
use_boardSeq.append(str(post).split("&")[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")
마치며
# -*- 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("&")[2]) # 가장 최신 공지랑 같은지 검사
use_boardSeq.append(str(post).split("&")[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개)가 동시에 날라오는 것을 보고 참... 많은 생각이 들었다.
테스트 코드 뿐만 아니라 실 서비스 코드에서도 확인을 꼭 잘하자.
귀찮고 힘들어도 두번 세번 확인해야 한다.
안그러면 대참사가 일어날테니까...
그리고 그림판을 너무 오랜만에 사용해서 도형들이 별로 이쁘지 않다.
원래는 더 자유롭게 사용했었는데..
뭐 하다보면 나아지겠지.
Ghost