이번 여름방학에 진행한 알림 프로젝트의 마지막 작업이다. 코드를 다듬고 서버에 올려서 돌리고자 한다.
###############################################################################
# -*- coding: utf-8 -*-
#
# Copyright 2021 by 황재경. All rights reserved.
#
# File name : BookAlarmProject.py
# Description : 책 제작 봉사활동 홈페이지에 제작 가능한 새로운 도서가 올라오면
# Slack 앱으로 메시지를 보낸다.
# Note :
# 1. BOT_TOKEN는 보안상의 이유로 웹에 올린 코드에서는 *로 대체하여 표시했다.
# 2. 변경가능한 값들은 PARAMETERS section에서 수정가능하다.
#
# History :
# * 2021-08-16
# - First created by 황재경
# * 2021-08-18
# - Modified by 황재경
# - 책 등록은 한 번만 하도록 코드 추가(같은 책에 대해 여러 번 알림x)
# * 2021-08-19
# - Modified by 황재경
# - 설정한 시간에 프로그램을 종료하는 코드 추가
# - 코드 전체적으로 다듬고 정리, 주석 추가
# * 2021-08-24
# - Modified by 황재경
# - 코드의 실행 여부를 핸드폰으로 쉽게 확인할 수 있도록 Slack으로 알림을
# 보내주는 text_message함수 추가
# - 시간 내에 작업이 이루어지지 않아 반환된 페이지는 반영하지 않기 위해
# "if int(numbers[0]) >= int(numbers[1]) // 2:"로 코드 수정
#
###############################################################################
import requests
import json
from bs4 import BeautifulSoup
import re
import time
from datetime import datetime
import os
###############################################################################
#
# PARAMETERS
#
###############################################################################
BOT_TOKEN = 'xoxb-*************-*************-************************'
EXIT_HOUR = 21 # [시, hour] 프로그램을 종료시킬 시간 - 현재 오후 9시로 설정
TIME_INTERVAL = 60*5 # [초, second] 홈페이지를 크롤링하는 시간 간격
###############################################################################
#
# FUNCTIONS
#
###############################################################################
def text_message(token, channel_id, text):
url = "<https://slack.com/api/chat.postMessage>"
headers = {
"Content-type": "application/json; charset=utf-8",
"Authorization": "Bearer " + token
}
data = {
"channel": channel_id,
"text": text
}
response = requests.post(url, headers=headers, data=json.dumps(data))
print(response)
if response.json().get('ok'):
print('메시지를 성공적으로 보냈습니다.\\n')
else:
print('메시지를 성공적으로 보내지 못했습니다. 오류메시지 : ' + str(response.json()))
def post_message(token, channel_id, info):
url = "<https://slack.com/api/chat.postMessage>"
headers = {
"Content-type": "application/json; charset=utf-8",
"Authorization": "Bearer " + token
}
data = {
"channel": channel_id,
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": info["text"]
}
},
{
"type": "image",
"image_url": info["img_url"],
"alt_text": info["title"]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "책 제작 신청하기",
"emoji": True
},
"style": "primary",
"value": info["book_url"],
"action_id": "actionId-0"
}
]
}
]
}
response = requests.post(url, headers=headers, data=json.dumps(data))
print(response)
if response.json().get('ok'):
print('메시지를 성공적으로 보냈습니다.\\n')
else:
print('메시지를 성공적으로 보내지 못했습니다. 오류메시지 : ' + str(response.json()))
bookAlarm_list = []
def alarm_books():
current_time = datetime.now()
if current_time.hour >= EXIT_HOUR:
return False
else:
print('-' * 15)
print(current_time.strftime('%H시 %M분 %S초'))
print('-' * 15)
site_url = "<https://www.itlo.org>"
url = "<https://www.itlo.org/booking>"
r = requests.get(url) # url에 접근
# 참고로 r.text는 binary, r.content는 str형태의 데이터이다.
bs = BeautifulSoup(r.content, "lxml") # "lxml"이라는 parser가 앞서 가져온 데이터에서 html요소로 접근
divs = bs.select("div.col-xs-12.col-md-6.col-lg-3.cropbkiddata") # select의 결과 = 리스트
for d in divs:
images = d.select("img")[0]
image = images.get("src")
img_url = site_url + image
ahref = d.select("a")
link = ahref[0].get("href")
link_url = site_url + link
title = ahref[1].select("font")[0].text # 태그 사이의 글은 text로 접근 가능
b_url = site_url + link
b_r = requests.get(b_url)
bbs = BeautifulSoup(b_r.content, "lxml")
b_pages = bbs.select("span")[-1].text
numbers = re.findall(r'\\d+', b_pages)
global bookAlarm_list
has_no_history = title not in bookAlarm_list # 이미 등록 알림을 보낸 책인지 확인
log_check = "bookAlamr_list = {ba_l}, current_book = {c_b}, has_no_history = {result}".format(
ba_l=bookAlarm_list, c_b=title, result=has_no_history
)
print(log_check)
print(numbers)
if int(numbers[0]) >= int(numbers[1]) // 2::
if has_no_history:
bookAlarm_list.append(title)
text_form = "*[NEW!!] 제작가능한 도서 등록됨* :loudspeaker:\\n제목:{title}\\n{book_pages}"
text = text_form.format(title=title, book_pages=b_pages)
info = {"text": text, "title": title, "book_url": link_url, "img_url": img_url}
return info
else:
break
print("새로 등록된 도서가 없습니다.")
###############################################################################
#
# MAIN CODE
#
###############################################################################
if __name__ == "__main__":
try:
print("To Exit, press 'CTRL+C'.")
text_message(BOT_TOKEN, "#봇-테스트", "프로그램 시작")
while True:
book_info = alarm_books()
if book_info is False:
break
print("book_info =", book_info)
if book_info is not None:
post_message(BOT_TOKEN, "#봉사활동-도서-알림", book_info)
time.sleep(TIME_INTERVAL)
except Exception as e: # 에러 종류
print('다음과 같은 오류가 발생했습니다:', e)
text_message(BOT_TOKEN, "#봇-테스트", "프로그램 오류 발생: {error}".format(error=e))
except KeyboardInterrupt:
print("You pressed 'CTRL+C'. Stop running program.")
text_message(BOT_TOKEN, "#봇-테스트", "프로그램 종료")
finally:
print("End program.")
text_message(BOT_TOKEN, "#봇-테스트", "프로그램 종료")
print(os.system("exit"))
윈도우 인스턴스가 잘 생성된 것을 확인할 수 있다. 인스턴스 상태도 '실행 중'으로 되었다.