1. 사용 배경

  기술적인 구현 내용을 알고 싶다면 바로 2번으로 건너뛰면 된다. 

 

 인턴으로 다니던 회사에서 데이터를 주기적으로 크롤링하고, 크롤링한 자료를 이용자들에게 제공해주는 웹 서버를 개발했다. 이 서버를 운영해야 하는데, 이 모든 작업이 정상 작동하는지 주기적으로 확인을 해야 한다. 서버는 사실 바로 안다. 그냥 주소를 입력했는데 안 들어가지면 문제가 있다. 내가 확인하기도 전에 아마 사용자 측에서 뭔가 피드백이 들어올 것이다.

 

 하지만, 크롤링이 잘못되었을 경우엔 이 크롤링이 잘못되었다는 사실을 깨닫는 건 상대적으로 까다롭다. 그냥 데이터가 몇 개 비거나, 혹은 아예 안 보일 뿐이지 다른 모든 건 정상작동 할 것이다. (서버와 크롤링 모듈을 합쳐놓지 않은 이상) 물론, 서버 로그를 확인하는 방법도 있다. 하지만 AWS를 사용하는 지금으로선 putty를 켜고 들어가는 거 자체도 너무 귀찮았다. 그래서 그냥 이 과정을 자동화 시켜서, 나는 그냥 앉아서 정상 작동 여부를 알도록 했다.

 

 

 

 해당 크롤링이 성공이든, 실패든 일단 무조건 메세지를 보내서 성공/실패를 보고하도록 하고 싶었다. 일단 언어는 크롤링 모듈이 python으로 만들어 졌기에 python으로 고정하고, 이제 이 메세지를 보낼 매체를 선정하는데, 선정 결과는 다음과 같았다.

 

- 이메일 : 프로토콜도 있고 다 있는데, 이미 내 네이버 메일함은 999+라 메일 공해를 더 일으키고 싶진 않다.  그렇다고  업무용 메일을 또 만들기엔 솔직히 내가 확인을 자주 안 할거같다.

 

- 카카오톡 : 접근성도 최고고, 다 좋은데 사적 메신저를 업무용으로 쓰고 싶진 않았다. 결정적으로, 현재 메세지 보내는 API 서비스가 종료하고 새로운 챗봇 서비스를 준비하는 모양인데, 이게 2020년 2월 기준으로 베타 테스트 중이라 사유를 적어서 신청해야 쓸 수 있다.

 

- 텔레그램 : telegram이라는 모듈이 있어서 코딩하기엔 편리한데, 이것도 사실 업무용보다는 사적인 메신저라 업무용으로 쓰고 싶진 않았다.

 

- Slack : slacker라는 쓰기 쉬운 모듈도 있고, 원래 업무용으로 나온 메신저다. 

 

 그래서 Slack bot을 만들기로 했다.

 

 

2. Slack bot 추가하기

 

 일단 당연히 Slack 워크스페이스를 만들어야 한다. 다행히 만드는 건 무료다. 무료 기능은 검색 기능이 15000 메세지로 제한되는 점을 빼면 개인적으로 사용하는 수준에선 딱히 지장이 없을 것이다. 사실 실무에서 slack을 사용하고 있었다면 이미 유료 에디션일 것이다.

 

 

 일단, 봇은 여기서 app이라고 부르는 데 이 app을 만들어야 한다. 

https://api.slack.com/ 여기로 본인 아이디로 로그인하고, 화면 중앙의 Start building -> Create New App을 클릭한다.

 

 

 

본인이 쓸 앱 이름과 이 앱이 적용될 워크스페이스를 지정한다. 아까 만든 워크스페이스를 지정하면 될 것이다.

 

 

 

 이제 앱은 생성되었고, 설정을 할 시간이다. 보면 bots와 같이 사용자와 인터렉션을 할 수 있도록 다양한 옵션이 준비되어 있는데, 지금 만드는 것은 양방향 소통이 아니라 일방적으로 정보를 전송해 주는 구독형 봇이라 Permissions만 손대보자.

 

 

  

 

 

 

들어가면 우선 Install App to Workspace라고 이름만 보면 반드시 눌러야 할 거 같이 생겼는데 안 눌리는 버튼이 있다. 당황하지 말고 지금은 권한이 아무것도 지정이 안 되어서 설치를 못 하는 것이다. 스크롤을 내리자.

 

 

 

 

밑에 Scopes에서, 우리는 Bot으로 쓰는 거니 Bot Token Scope에서 지정해보자. 여러 가지 권한들이 있는데, 내가 사용할 것은 chat:write 다. chat:write는 당연히 글을 써야 하니 주는 기능이다. 클릭하면 바로 적용이 된다.

 

 

 이외에도 사용 가능 IP 제한 설정이나 리디렉션 설정 등이 있다. 필요한 설정이 있다면 하면 되고, 이제 다시 보면...

 

 

 

 이제야 인스톨 앱 버튼이 활성화가 됐다. 다음엔 이런 권한을 준다고 확인창이 뜨는데, 읽어보고 자기가 준 권한이 맞다면 승인을 누르자.

 

 

 

 

 

 Bot 토큰을 받는데, 이 토큰으로 봇을 조종하니 유출되지 않게 하자.

그리고 이 봇이 들어갈 채널을 만들자. 테스트인데 그냥 #test면 심심하니까 #testtest로 했다.

 

 

 

눌러야 하는 버튼이 보인다. Add an app을 누르자.

 

 

 

 아까 만든 TestService가 보인다. Add를 하면 채널에 초대가 된다.

그런데, 처음 만들 땐 Add가 아니라 그냥 View로 보이면서 초대가 안 될 때가 있다. 이 경우는 그냥 기다리니까 다 해결이 됐는데, 내 추측으로는 앱이 만들어졌다는 사실 자체는 바로 전송되는데, 앱이 해당 워크스페이스에 적용되는 데는 시간이 걸리는 것 같다. 

 

 

 

 

초대가 됐다. 참고로 아이콘이나 이름을 편집하고 싶다면, 앱 관리 페이지의 Basic Information에서 이름이나 아이콘 등을 바꿀 수 있다.

 

 

 이걸로 모든 준비가 끝났다.

 

 

 

3. Python 코드 작성

 

 pip install slacker를 통해 라이브러리를 설치한다.

 

사용은 놀랍도록 간단하다.

 

# Slackbot.py

from slacker import Slacker
from datetime import datetime

token = '아까 받은 토큰을 이곳에 삽입'


def send_slack_message(msg, error=''):
    full_msg = msg
    if error:
        full_msg = msg + '\n에러 내용:\n' + str(error)
    today = datetime.now().strftime('%Y-%m-%d %H:%M:%S ')
    slack = Slacker(token)
    slack.chat.post_message('#testtest', today + full_msg, as_user=True)

if __name__ == "__main__":
    # 일부러 에러가 발생하도록 함.
    try:
        a = 1/0
    except Exception as e:
        send_slack_message('테스트', e)

 사실 메세지 전송 자체는 딱 import 한 줄, token 한 줄, slacker 두 줄로 총 네 줄이면 충분한데, 나는 해당 에러 발생 시간도 알기 위해 시간 정보 역시 삽입했다. 아래의 __main__ 코드를 통해 에러를 발생시켜보자.

 

 

 다음과 같이 메세지가 보내어진다. 기본적으론 이게 끝이다!

 

4. 프로젝트에서의 활용

 

 이번엔 내가 사용했던 용례를 소개하고자 한다. 우선, 가장 기본적으론 이렇게 사용할 수 있겠다.

 

def run():
    # 여기에서 크롤링 진행


if __name__ == "__main__":
    try:
        run()
        send_slack_message('크롤링이 완료되었습니다.')
    except Exception as e:
        send_slack_message('크롤링 작업 중 오류가 발생했습니다!', e)

 

 이러면 run()이 오류 없이 진행되었을 시, 크롤링이 완료되었습니다 라는 메세지가 보내지고, 에러가 나면 에러내용과 함께 에러가 발생했다는 메세지가 전송된다. 성공을 하든 실패를 하든 일단 메세지를 보내게 된다. 만약 정해진 시간에 아무런 메세지도 안 온다? 이러면 아예 실행 자체가 안 된 것이니 역시 서버를 체크하면 된다. 성공의 경우도 메세지를 보내야 하는 이유가, 서버가 OS 다운 등의 모종의 사유로 아예 스크립트를 못 돌리는 상황도 체크하기 위함이다.

 

 

 

 하지만 이 경우는 단순히 크롤러의 성공/실패 여부만 따질 뿐, 문제가 있다. 말 그대로 프로그램을 돌릴 수 없는 '에러' 만 잡는 상황이고, 의도하지 않는 값이 나오는 경우는 잡을 수 없다. 즉 함수에 들어가야 할 값이 안 들어가고 None이 들어가서 None이 나오는 경우, 우리가 의도한 상황이 아니지만 아무튼 에러는 안 난다. 이런 경우를 위해, 사용자 정의 에러를 사용하자. 

 

from Slackbot import send_slack_message

class LoginError(Exception):
    def __init__(self, target_name):
        self.msg = '{target} 로그인 양식이 맞지 않습니다.'.format(target=target_name)
        send_slack_message(self.msg)
    def __str__(self):
        return self.msg


class DBError(Exception):
    def __init__(self, name, length, error):
        self.msg = '{name} 에 대한 {len} 행의 데이터 입력 작업 중 오류가 발생했습니다.\n{error}'\
            .format(name=name, error=error, len=length)
        send_slack_message(self.msg)
    def __str__(self):
        return self.msg

 

 코드 내에서 값의 유효성을 검증하는 부분을 추가하고, 유효하지 않을 경우 위와 같은 에러들을 raise 한다. 이 에러들은 raise 됨과 동시에 __init__ 에 의해 슬랙으로 에러 내용에 대해 정해진 양식의 메세지를 보내게 된다. 나는 실제 코드에선 Slack API가 작동하지 않을 것을 대비하여, 위의 코드에선 표시하지 않았지만 logging 모듈을 이용해 로컬 파일로 에러 로그를 작성하는 부분도 매 에러마다 추가해 두었다. 

 

 

 

 이외에도, 메세지는 보내되 크롤링은 그대로 진행해야 하는 경우도 있다. 1000개 중 하나만 오류가 난다면, 그 하나 때문에 크롤링이 완전히 중단되는 것보단 999개는 진행하고 1개는 나중에 처리하는 것이 합리적일 것이다. 그런 경우에도 간단히 이런 식으로 작성하면 될 것이다.

 

for name in name_list:
    try:
        crawl_something(name)
    except:
        send_slack_message('크롤링 중 {name} 를 처리하는 데 오류가 발생했습니다.'.format(name=name))

 개인적으론 이 방식은 항목 하나라도 크롤링이 안되면 치명적이거나, 데이터 수가 매우 적은 경우를 제외하곤 추천하지 않는다. 천개짜리 name_list를 크롤링을 진행하는데 모두 오류가 나서 천 개의 메세지가 왔다고 가정하면... 끔찍하다. 차라리 실패한 갯수(except 블록이 실행된 횟수)를 카운팅해서 그 갯수를 보내주는 게 합리적일 것이다. 내 경우엔 일단 몇 개 정도는 당장 크롤링에 실패해도 서비스에 큰 지장은 없어서, except 블록에서 해당 에러를 로그 파일에 기록만 하도록 작성해 두었다.

 

 

 

5. 주의사항

 위의 방법은 오류가 났다는 걸 능동적으로 알려주기만 할 뿐, 절대 오류를 해결해 주는 과정이 아님을 명심하자. 따라서 위에서는 가독성을 위해 생략했지만 문제 원인 파악과 해결을 위해선 logging 등의 기본적인 로그 작성은 필수다. 그리고, 유효성 검증을 적절하게 할 수 있도록 코드를 짜는 것 역시 중요하다.

 

 

 

 

 

+ Recent posts