동기

이번 여름방학에 진행한 알림 프로젝트의 마지막 작업이다. 코드를 다듬고 서버에 올려서 돌리고자 한다.

과정

추가할 기능

ERRORS

###############################################################################
# -*- 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"))

Untitled

Untitled

Untitled

Untitled

윈도우 인스턴스가 잘 생성된 것을 확인할 수 있다. 인스턴스 상태도 '실행 중'으로 되었다.

윈도우 인스턴스가 잘 생성된 것을 확인할 수 있다. 인스턴스 상태도 '실행 중'으로 되었다.