TIL105. Django : Django Crontab 을 이용하기

20604 단어 djangodjango

📌 이 포스팅에서는 crontab 기능을 Django에서 사용하여 정기적으로 로직이 수행되게하는 과정에 대해 정리하였습니다.



🌈 Django Crontab 을 이용하기

🔥 Django Crontab 이란?

🔥 Django Crontab 설정 방법

🔥 Django Crontab으로 정기적으로 요청보내기



1. Django Crontab 이란?

🤔 crontab 이란?

✔️ crontab은 정기적으로 프로그램을 실행시켜주기 위해 사용하는 기능으로 linux에 포함되어 있다.

✔️ 이에 정기적으로 프로그램 실행시켜서 업데이트를 하거나, 메일을 보낸다거나 할 때 cron을 활용한다.

✔️ Django에서는 이러한 기능을 손쉽게 사용할 수 있도록 Django-crontab을 제공하고, 이를 사용하기 위해서는 설치 후 app 등록을 해야 한다.

$ pip install django-crontab

# Application definition
DJANGO_APPS = [
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
PROJECT_APPS = [
    'core',
    'users',
    'rooms',
    'contents',
]
THIRD_PARTY_APPS = [
    'corsheaders',
    'django_crontab',
]
INSTALLED_APPS = DJANGO_APPS + PROJECT_APPS + THIRD_PARTY_APPS


2. Django Crontab 설정 방법

🤔 간단한 정기적 실행 해보기

✔️ 아래 함수를 매분 실행시켜보도록 하자. 이를 위해 settgins.py 맨 하단에 crontab을 어느 주기로 실행시킬지 설정해주어야 한다.

def crontab_every_minute():
    print('hello crontab')

✔️ 위 함수가 core 앱에 cron.py 파일에 작성되어 있다고 가정하면, 아래와 같이 설정해주면 된다.

CRONJOBS = [
    ('*/1 * * * *', 'core.cron.crontab_every_minute', '>> '+os.path.join(BASE_DIR, 'config/log/cron.log'),
]

✔️ core앱에 cron.py의 해당 함수를 BASE_DIR 기준으로 config/log/cron.log 파일에 찍히도록 하는 설정이다.

✔️ 이렇게 여러 작업을 CRONJABS에 list형식으로 지정해주면, 해당 함수가 정기적으로 작동되고, 이에 대한 log파일을 생성해준다.

✔️ 단, 위에 설정은 성공했을 경우에만 log파일에 실행결과가 찍히게 되는데, 에러에 대해서도 확인하고 싶다면 아래처럼 설정해야 한다.

CRONJOBS = [
    # ('*/1 * * * *', 'core.cron.crontab_every_minute', '>> '+os.path.join(BASE_DIR, 'config/log/cron.log'),
    ('*/1 * * * *', 'core.cron.crontab_every_minute', '>> '+os.path.join(BASE_DIR, 'config/log/cron.log')+' 2>&1 ')
]

🤔 crontab 실행 주기 설정 방법

✔️ 위 cron 설정에서 /1 * * *이 매분(1분) 1번씩 이라는 의미이다. 띄어쓰기 기준으로 스케쥴을 지정할 수 있다.

✔️ 스케쥴의 순서는 *(몇분) *(몇시) *(몇일) *(몇월) *(몇주) *(몇년) 순이다.

✔️ 링크(https://crontab.guru/#*_*_*_*_*)에 들어가면, crontab 스케쥴에 대해 연습을 해볼 수 있다.

✔️ 이 밖에도 콤마(,)를 찍거나, 대쉬(-)를 사용해서 간격을 지정하거나 범위를 설정할 수 도 있다.

✔️ 예를 들어, 0시, 6시, 12시, 18시 정각을 기준으로 매일 4번에 거쳐 어떤 로직을 실행시키고 싶다면 아래 처럼 지정하면 된다.

CRONJOBS = [
    ('0 0,6,12,18 * * *', 'core.cron.crontab_every_minute', '>> '+os.path.join(BASE_DIR, 'config/log/cron.log')+' 2>&1 ')
]

🤔 django-crontab 명령어

✔️ crontab 업무 추가

$ > python manage.py crontab add

✔️ crontab 실행 중인 업무 보기

$ > python manage.py crontab show

✔️ crontab 업무에서 제거

$ > python manage.py crontab show

🤔 Operation not permitted 에러가 발생할 때

✔️ 위에 django crontab 설정을 했는데도 불구하고, log파일에 아무것도 찍히지 않는다면 우선 error 메시지를 log에 찍게하는 것이다.

✔️ 어떤 로직상의 문제이면 쉽게 해결되지만, Mac에서 "Operation not permitted"이 발생된다면 추가 설정이 필요하다.

✔️ Mac은 보안이 철저하기 때문에 permission 관련 이슈가 발생한다면 아래와 같이 설정을 통해 crontab이 실행될 수 있도록 처리해야한다.

✔️ 우선 설정의 "보안 및 개인정보 보호"로 들어가, "개인 정보 보호" 탭에 "전체 디스크 접근 권한"으로 들어간다. 여기에 cron이라는 프로그램이 허가를 받지 못해서 일어난 일이다. 아래 cron이 체크된 이유는 이미 허가를 줘서이고, 그 방법에 대해 아래를 참고하자.

✔️ 권한을 주기 위해 우선 자물쇠를 풀고 하단에 + 버튼을 누르면 파일 탐색기가 나온다. 여기서 "shift + command + G"를 한 뒤, /usr/sbin/cron 입력하면 cron 이름을 가진 터미널 모양의 아이콘을 나온다. 이를 누른다.

✔️ 그러면 위에처럼 cron이 생긴다. cron에 체크를해주고 자물쇠를 닫아준 뒤 나오면 permitted 에러가 해결된다.



3. Django Crontab으로 정기적으로 요청보내기

🤔 정기적으로 Youtube API에 요청보내기

✔️ Youtube API는 사용에 있어 할당량이 있기 때문에 자유롭게 사용하는게 한계가 있다.

✔️ 기업 협업 과정에서 Youtube 인기 동영상을 하루에 4번 요청하여 DB에 저장시켜달라는 요청이 있었고, 받아온 데이터들이 각 각 필요한 Table에 저장해야 한다.

✔️ 또한 여기서 중요한 점은 이미 저장된 콘텐츠는 저장시키지 않아야하고, Tag같은 경우 업로드한 사람이 직접 지정한 Tag가 아니라, Youtube에서 지정한 tag를 활용해야 한다.

✔️ 특히, Category 관련해서 중복이 발생되지 않고 Foreign Key로 저장되어야하는 값은 이미 저장된 데이터를 여러 콘텐츠가 참조해야하기 때문에 이미 있는 값인지도 확인해서 Data가 적재적소에 잘 저장될 수 있게 고민해야한다.

import requests
from datetime        import datetime
from django.conf     import settings
from contents.models import Content, ContentCategory, Tag, ContentTag
# 정지적으로 실행될 youtube api 로직
def popular_videos_get_youtube_api():
    search_url = 'https://www.googleapis.com/youtube/v3/videos'       
    params = {
        'part'       : 'snippet,statistics,player,contentDetails,topicDetails',
        'chart'      : 'mostPopular',
        'regionCode' : 'KR',
        'maxResults' : 50,
        'key'        : settings.YOUTUBE_DATA_API_KEY,
    }
    data  = requests.get(search_url, params=params).json()
    items = data['items']
    # category table 저장
    youtube_obj, created = ContentCategory.objects.get_or_create(name = 'youtube')
    count = 0  # 👈 log 파일에 몇 개를 저장됬는지 파악하기 위해 count 변수 선언
    for item in items:
        if not Content.objects.filter(content_link_url = 'https://www.youtube.com/embed/'+item['id']).exists():
            obj = Content.objects.create(
                title                 = item['snippet']['title'],
                content_link_url      = 'https://www.youtube.com/embed/' + item['id'],
                thumbnails_url        = item['snippet']['thumbnails']['medium']['url'],
                running_time          = item['contentDetails']['duration'][2:],
                view_count            = item['statistics'].get('viewCount'),
                like_count            = item['statistics'].get('likeCount'),
                dislike_count         = item['statistics'].get('dislikeCount'),
                channel_id            = item['snippet']['channelId'],
                channel_title         = item['snippet']['channelTitle'],
                published_at          = item['snippet']['publishedAt'],
                content_categories_id = youtube_obj.id,
            )
            count += 1
            # topicDetails 값이 있는 경우에만 tag 테이블 및 content_tag 테이블에 값을 저장
            if item.get('topicDetails'):
                tags = item['topicDetails']['topicCategories']
                for tag in tags:
                    tag_obj, created = Tag.objects.get_or_create(name = tag.split('/')[-1])
                    ContentTag.objects.create(contents_id = obj.id, tags_id = tag_obj.id)
    print(f'{datetime.now()} : {count}/{len(items)} data created') # 👈 log파일에 찍힐 내용

🤔 느낀점

✔️ 처음 crontab이 실행될 때 마다 아무런 출력값을 확인할 수 없어 많은 시간을 할 애 했다. print에 대한 값이 터미널에 출력되거나, 아니면 에러메시지라도 출력될줄 알았는데 아무런 반응이 없어 여기저기를 뒤져보다 cron에 permitted를 주어야한다는 것을 확인하고 해결했다.

✔️ 원하는 로직이 정기적으로 실행되었다해도, 실제 이렇게 youtube API에 요청하게 될 경우, 반환받는 데이터마다 값이 존재하지 않는 경우도 있기 때문에 초반에 error 로그를 잘 파악해야한다.

좋은 웹페이지 즐겨찾기