Python

[Python] 학식 봇 만들기(Crawling, Slack, Webhook)

mayhun28 2024. 12. 27. 18:51

이전에 학식 봇을 만든적이 있다. 이유는 사실 별 생각 없이 학식을 먹을지, 메뉴를 보고 별로다 생각이 들면 외부 음식점을 갈지 하곤 했는데 매일 학식 확인하는 페이지를 들어가 확인하는 것도 귀찮았다. 

 

코드는 유실되어 없지만 지금 시점에서 재구현을 해보려 한다.

 


 

모교의 학식 메뉴 확인하는 페이지 구성은 이렇다. 현재 종강을 하여 메뉴가 없다..

그렇다면 교직원 식당으로 학식봇(교식봇)을로 구성하겠다.

 

 


 

Selenium 기본 코드

크롤링은 selenium을 통해 할것이다. 크롤링에는 requests를 통한 정적 크롤링도 가능하겠지만, 해당 페이지는 동적으로 동작하는 클라이언드 사이드 렌더링 되는 페이지이기 때문이다.

 

라이브러리 install

pip install selenium webdriver_manager
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager



USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'

chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument(f'user-agent={USER_AGENT}')
chrome_options.add_argument('--headless')
chrome_options.add_argument('--window-size=1920x1080')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--disable-javascript')

driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)

# 페이지 이동
driver.get('https://www.pcu.ac.kr/kor/29/diet')

 

 

해당 코드를 실행 하면 아래 이미지와 같이 'Chrome이 자동화된 테스트 소프트웨워에 의해 제어되고 있습니다.' 라고 뜨며 chrome 가 실행 된다. 

 

페이지 이동

여기서 현재는 교직원 식당의 메뉴를 확인 해야함으로 교직원 식당 버튼을 클릭 해야한다.

 

클릭 이벤트는 직접 요소를 클릭해도 되고, onclick으로 되어있다면 javascript를 따로 실행 할수 있다.

 

ctrol + shift + c 를 누르면 요소를 선택해 개발자 도구에서 확인할수 있다.

 

xpath를 복사 한 후 

 

해당 요소의 XPATH는 //*[@id="faculty"] 이다.

find_element와 click을 사용하여 클릭 이벤트를 실행 할수 있다.

driver.find_element(By.XPATH, '//*[@id="faculty"]').click()

 

또는 

onclick 이벤트의 javascript를 실행 할수 있다. 해당 요소는 아래와 같이 구성되어있다.

<a href="#" id="faculty" onclick="diet.second(); return false;" title="선택됨"><span>교직원식당</span></a>

 

selenium 에서 javascript 실행 하는것은  execute_script()를 사용하면 된다.

diet.second(); 함수를 실행 시키도록 한다.

driver.execute_script("diet.second();")

 

 

메뉴 가져오기

 

메뉴 구성은 <tr> 안에 <td>로 이루어져있다. 5개에 대한 xpath를 가져와보면 아래와 같다. 월요일 부터 금요일까지 1부터 5까지 증가를 한다.

# 월
'//*[@id="txt"]/div[8]/div/div/table/tbody/tr/td[1]/div'
# 화
'//*[@id="txt"]/div[8]/div/div/table/tbody/tr/td[2]/div'
# 수
'//*[@id="txt"]/div[8]/div/div/table/tbody/tr/td[3]/div'
# 목
'//*[@id="txt"]/div[8]/div/div/table/tbody/tr/td[4]/div'
# 금
'//*[@id="txt"]/div[8]/div/div/table/tbody/tr/td[5]/div'

 

 

오늘의 요일을 구분하기 위해 datetime을 사용하여 알아낼수 있다. 현재 시간의 weekday()를 사용하면, 월요일부터 일요일까지 0부터 6까지 숫자로 요일을 표현 할 수 있다. 1식 증가 시켜 메뉴 요소와 숫자를 일치 시킨다.

from datetime import datetime

today = datetime.now()
weekday_index = today.weekday() + 1  # 1: 월, 2: 화, ..., 5: 금

 

find_element와 xpath, f-string을 통해 weekday_index를 통해 메뉴 텍스트를 가져온다.

today_menu = driver.find_element(By.XPATH, f'//*[@id="txt"]/div[8]/div/div/table/tbody/tr/td[{weekday_index}]/div').text

 

 

현재까지의 전체 코드

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager  # 추가 필요
from datetime import datetime
from time import sleep



USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
    

def get_driver():
  chrome_options = webdriver.ChromeOptions()
  chrome_options.add_argument(f'user-agent={USER_AGENT}')
  chrome_options.add_argument('--headless') 
  chrome_options.add_argument('--window-size=1920x1080')
  chrome_options.add_argument('--no-sandbox')
  chrome_options.add_argument('--disable-gpu')
  chrome_options.add_argument('--disable-javascript')


  driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
  return driver


def run():
  driver = get_driver()
  driver.get('https://www.pcu.ac.kr/kor/29/diet') # 식단 페이지 이동
  driver.execute_script("diet.second();")         # 교식원 식당 페이지 이동
  
  sleep(2)

  today = datetime.now()
  weekday_index = today.weekday() + 1  # 0: 월, 1: 화, ..., 4: 금
  today_menu = driver.find_element(By.XPATH, f'//*[@id="txt"]/div[8]/div/div/table/tbody/tr/td[{weekday_index}]/div').text
  print(today_menu)


if __name__ == '__main__':
  run()

 

실행을 하면, 아래와 같은 결과가 출력 된다.

 


Slack bot 만들기

1. https://api.slack.com/ 접속 후 우측 상단에 Your apps를 클릭한다.

2. Create New App 클릭

 

3. From scratch 클릭

 

4. App Name와 추가할 workspace를 선택 후 create app을 클릭하면 app이 완성 된다.

5. incoming Webhooks에서 off -> on 으로 변경 후 Add New Webhook to workpace 클릭

 

오류

이전에 설정할땐 없던 방법인데, 위 incoming webhooks의 글을 다시 읽어보니 bot user가 필요하다고 한다. bot user의 링크를 클릭하면 app home 으로 넘어간다.

Edit 클릭 후 내용 작성 후 Add를 해준다.

 

 

 

그리고 Incoming Webhooks로 넘어와 Add New Webhook to Workspace를 클릭하면 아래와 같은 화면이 나오고, bot을 추가할 채널을 선택 후 허용을 해준다.

허용을 해주면 webhook URL이 생긴다.

 

Web Hook 메시지 보내기

아래 링크에서 web hook 문서를 확인 할수 있다.

https://tools.slack.dev/python-slack-sdk/webhook/

 

Webhook Client | Python Slack SDK

Incoming Webhooks

tools.slack.dev

 

 

url에 위에서 만든 app의 webhookurl을 넣으면 된다. 

 

테스트

from slack_sdk.webhook import WebhookClient
url = "{web hook url}"
webhook = WebhookClient(url)

response = webhook.send(text="Hello!")
assert response.status_code == 200
assert response.body == "ok"

 

위 코드를 실행하면 아래와 같이 메시지가 온다.

 

lunch_bot 코드 수정

def run():
  driver = get_driver()
  driver.get('https://www.pcu.ac.kr/kor/29/diet') # 식단 페이지 이동
  driver.execute_script("diet.second();")         # 교식원 식당 페이지 이동
  
  sleep(2)

  today = datetime.now()
  weekday_index = today.weekday() + 1  # 0: 월, 1: 화, ..., 4: 금
  today_menu = driver.find_element(By.XPATH, f'//*[@id="txt"]/div[8]/div/div/table/tbody/tr/td[{weekday_index}]/div').text
  
  # slack 전송
  webhook.send(text=today_menu)

 

정상적으로 전송이 된다. 

좀 더 보기 좋게 마크다운 텍스트 스타일로 수정 하고, 예외 처리를 해본다.

def run():
    driver = get_driver()
    driver.get('https://www.pcu.ac.kr/kor/29/diet')  # 식단 페이지 이동
    driver.execute_script("diet.second();")          # 교식원 식당 페이지 이동

    sleep(2)

    today = datetime.now()
    date_string = today.strftime('%Y-%m-%d (%A)')  # 오늘 날짜와 요일 (e.g., 2024-12-27 (Friday))
    weekday_index = today.weekday() + 1  # 0: 월, 1: 화, ..., 4: 금

    try:
        today_menu = driver.find_element(By.XPATH, f'//*[@id="txt"]/div[8]/div/div/table/tbody/tr/td[{weekday_index}]/div').text
        message = f"*{date_string} 오늘의 메뉴*\n```{today_menu}```"
    except Exception as e:
        message = f"{date_string}\n오늘의 메뉴를 가져오는 데 실패했습니다. 오류: {e}"

    webhook.send(text=message)
    driver.quit()

 

조금 더 깔끔 하게 금일 메뉴를 볼수 있다.

 


추가. 스케줄러 등록

운영 중인 홈서버에서 매일 11시 정각에 slack으로 메뉴 전송 하기

 

crontab을 사용하여 평일 11시 정각에 py 파일을 실행 하도록 설정 하면 된다.

 

crontab 수정

crontab -e

 

 

 

 

crontab 에서 순서대로 설명하면

  • m: 분
  • h: 시
  • dom (Day for Month): 일
  • mon (Month): 월
  • dow (Day for Week): 요일

즉, 월요일 부터 금요일까지 매일 11시에 command를 수행한다.

 

테스트를 위해 시간만 조정하여 crontab을 저장하였다. 따로 적용하는 명령어는 필요하지 않다.

 

50분으로 설정 후 기다리니 50분이 되어 전송 되었다.