본문 바로가기

MLOps/Development

전장연 알리미 (feat. 지하철 뭐 안 타지?) - 1

전장연 알리미

만약에 모든 지하철 호선을 타도 출근 시간이 똑같다면 어떤 지하철을 탈 것인가?

"음.. 곧 도착하는 지하철을 먼저 타지 않을까요?

맞는 말이다. 물론 전장연 아침 지하철 승하차 시위가 일어나기 전 까지는 말이다.

그렇다면 정답은

"시위를 하지 않는 호선의 지하철을 타는 것" 이, 지금 시대에 정답이다.

 

며칠 전까지 사당에서 출근해서 2호선과 4호선 중에 4호선만 고집하다 출근이 늦어진 나 때문에 만든 게 아니라고 절대 말하지 않는다.


프로세스

  1. 스크랩한다.
  2. 나에게 메시지를 보낸다.

전혀 어렵지 않은 코드와 단순한 과정만 있을 뿐이다.

 

확인해야 할 사항

  • 지하철 시위 및 지연과 같은 안내는 서울 도로 교통 공사홈페이지 공지사항에서 확인 가능
  • 시위전날에 공지가 올라옴
  • 최신 글 하나만 확인, BUT  'NOTICE' 경우에 그림 1처럼 항상 상단에 위치함
  • 그림 2처럼, 날짜가 범위로 된 경우가 있음
  • 게시글을 클릭하면, 그림 3으로 넘어가서 자세한 내용 확인 가능
  • 카카오 액세스 토큰 발급받기(이게 제일 귀찮은..)

그림 1
그림 2
그림 3

결과

  • 물론 전 날 공지가 없으면 없다고 보내주긴 한다.

코드

  • 코드는 주석을 달아 두겠습니다.
import requests
from bs4 import BeautifulSoup as bs
from datetime import datetime, timedelta
from collections import defaultdict
import json

# 서울도로 교통공사 메인 주소
seoul_metro_home_url = 'http://www.seoulmetro.co.kr/kr/'
# 공지사항
announcement_page = 'board.do?menuIdx=546'

# 공지사항 게시글 selector path
announcement_selector = '#contents > div.tbl-box1 > table > tbody'

# 그림 3의 내용에 해당하는 selector path
detail_selector  = '#board-top > article > div > table > tbody > tr:nth-child(3) > td > div.textarea-area > article > div'

# 글 제목에서 찾을 단어들
# 사실 '열차운행 지연 예정 및 무정차 통과 안내' 라고 고정되어 있는 것 같기도..
words_to_check = ['지연', '무정차']

# 전 날 지연 관련 공지 없을 때, 리턴할 메시지
is_no_notice_msg = '알림 없음'

# 날짜 형식이 1/4~1/6인 경우, 1/4, 1/5, 1/6 을 찾기 위함
def date_range(start, end):
    start = datetime.strptime(start, "%m/%d")
    end = datetime.strptime(end, "%m/%d")
    dates = [(start + timedelta(days=i)).strftime("%m/%d") for i in range((end - start).days + 1)]
    return dates

# 리턴 값은 Bool, 오늘 날짜에 해당하는 공지가 있는 지 확인하기 위함
def check_noticed_of_today(date_string):
    today = datetime.today().strftime('%m/%d')
    try:
        if "~" in date_string:
            start, end = tuple(map(lambda d: d.strip(), date_string.split('~')))
            dates = date_range(start, end)
            if today in dates:
                return True
        else:
            notice_date = datetime.strptime(date_string, "%m/%d").strftime('%m/%d')
            if today == notice_date:
                return True
    except ValueError as e:
        print(e)

# 
def get_response_body(url, selector_path):
    response = requests.get(url)
    if response.status_code != 200:
        raise Exception('Check Response')

    html = response.text
    soup = bs(html, 'html.parser')
    tbody = soup.select_one(selector_path)

    return tbody

if __name__ == "__main__":
    return_msg = defaultdict()
    url = seoul_metro_home_url+announcement_page
    tbody = get_response_body(url, announcement_selector)
    # 태그 종류는 직접 찾아야함
    tr_lists = tbody.select('tr')
	
    # NOTICE 인 경우, 제외 
    # 지연 안내가 NOTICE로 올라 올 수도 있는데, 일단 제외
    for tr in tr_lists:
        content = tr.select('td')
        text_number = content[0].get_text()
        if text_number.isdigit(): break
	
    # 글 제목과 그림 3으로 넘어가는 주소(href) 
    content_body = content[1].select_one('a')
    href = content_body.attrs['href']
    title = content_body.attrs['title']
	
    # 글 제목에서 날짜 가져옴. 정규식 XX
    date_string = title.rsplit('(')[-1].replace(")", "")
	
    # 오늘 날짜 공지 & 무정차, 지연 단어 있는 경우 스크랩
    if check_noticed_of_today(date_string) and any(word in title for word in words_to_check):
        url = seoul_metro_home_url + href
        tbody = get_response_body(url, detail_selector)
        return_msg['title'] = title
        return_msg['body'] = tbody.get_text()
    else:
        return_msg['title'] = is_no_notice_msg
        return_msg['body'] = is_no_notice_msg
    
    # 나에게 메시지 보내기
    response = requests.post(kakao_url, headers=headers, data=data)

 


이제 스케줄링 툴에 올려서 매일 알림을 받아야지. 끝이다~

 

라고 했지만, 그 발언 때문인지 

다음날 일어나서 보니 토큰은 만료된다는 사실을 까먹고 있었..

결론

  • 1편으로 끝내려던 게 갑자기 2편으로..