📌 이 포스팅에서는 crontab 기능을 Django에서 사용하여 정기적으로 로직이 수행되게하는 과정에 대해 정리하였습니다.
🔥 Django Crontab 이란?
🔥 Django Crontab 설정 방법
🔥 Django 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
✔️ 아래 함수를 매분 실행시켜보도록 하자. 이를 위해 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 ') ]
✔️ 위 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 ') ]
✔️ crontab 업무 추가
$ > python manage.py crontab add
✔️ crontab 실행 중인 업무 보기
$ > python manage.py crontab show
✔️ crontab 업무에서 제거
$ > python manage.py crontab show
✔️ 위에 django crontab 설정을 했는데도 불구하고, log파일에 아무것도 찍히지 않는다면 우선 error 메시지를 log에 찍게하는 것이다.
✔️ 어떤 로직상의 문제이면 쉽게 해결되지만, Mac에서 "Operation not permitted"이 발생된다면 추가 설정이 필요하다.
✔️ Mac은 보안이 철저하기 때문에 permission 관련 이슈가 발생한다면 아래와 같이 설정을 통해 crontab이 실행될 수 있도록 처리해야한다.
✔️ 우선 설정의 "보안 및 개인정보 보호"로 들어가, "개인 정보 보호" 탭에 "전체 디스크 접근 권한"으로 들어간다. 여기에 cron이라는 프로그램이 허가를 받지 못해서 일어난 일이다. 아래 cron이 체크된 이유는 이미 허가를 줘서이고, 그 방법에 대해 아래를 참고하자.
✔️ 권한을 주기 위해 우선 자물쇠를 풀고 하단에 + 버튼을 누르면 파일 탐색기가 나온다. 여기서 "shift + command + G"를 한 뒤, /usr/sbin/cron 입력하면 cron 이름을 가진 터미널 모양의 아이콘을 나온다. 이를 누른다.
✔️ 그러면 위에처럼 cron이 생긴다. cron에 체크를해주고 자물쇠를 닫아준 뒤 나오면 permitted 에러가 해결된다.
✔️ 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 로그를 잘 파악해야한다.
안녕하세요! 좋은 글 잘 봤습니다 :) 사소하지만 오타가 있어서 댓글 달아봐요. '2. Django Crontab 설정 방법' 파트에서 '✔️ crontab 업무에서 제거' 부분에 코드가 위에랑 똑같이 '$ > python manage.py crontab show'로 되어있네요. 유용한 포스팅 잘 보고 있습니다. 감사합니다!