Codong's Development Diary RSS 태그 관리 글쓰기 방명록
분류 전체보기 (48)
2021-03-31 21:08:26

개요


scraping을 진행하면서, beautiful soup으로 하나씩 셀렉터로 접근해서 a_tag의 href를 가져와서 그곳에 들어가서 데이터를 가져오는것이 목표였다.

문제는 속도가 너무 느린것이다..... 약 10000번 정도를 진행하는데, 너무 오래걸려서 다른 방법 없을까 찾다가, scraping을 위한 scrapy, autoscraper 등 다른 라이브러리들을 찾아봤다. 하지만 이미 쓰는거에서 Multiprocessing을 이용해서 사용하는 방법이 있길래 이것을 이용해보기로했다.

추가로 동시성을 활용한 Asyncio scraping에 대해서 알고싶은 분은 참고 부탁드립니다!

2021.08.07 - [python/Scraping] - [Python] 동시성(Concurrency) Asyncio scraping

 

[Python] 동시성(Concurrency) Asyncio scraping

개요 내가 예전에 멀티 프로세싱(Multi processing)을 이용한 scraping 방법에 대해 포스팅을 한 적이 있다. (궁금하신 분들은 아래 링크 참조) 2021.03.31 - [python/Scraping] - [Python] beautifulsoup multipr..

codong.tistory.com

 

Task


가져온 데이터를 종류별(ex. 제목:내용)로 dictionary로 구분해서 담고싶었다. 우선 코드부터 확인해보자.

import requests
from bs4 import BeautifulSoup
from multiprocessing import Pool, Manager
from itertools import repeat

def crawl_data(word,dic):
    url=f'https://www.coupang.com/np/search?component=&q={word}&channel=user'
    response = requests.get(url)
    soup=BeautifulSoup(response.text,'html.parser')

    div_=soup.find_all('div',id="name")
    # selector 부분 생략 ...

    dic[title]=price

if __name__=='__main__':
    manager = Manager()
    result_dic = manager.dict()

    # request의 parameter 리스트
    search_list=['삼겹살','불고기','오리고기','돼지고기', ... ]

    pool = Pool(processes=4) #4개의 프로세스 동시에 작동

    # 각 프로세스별로 딕셔너리 적용 시키기 위해 repeat 사용.
    pool.starmap(crawl_data, zip(search_list,repeat(result_dic)))
    print(result_dic)

위와 같이 크롤링 할 검색어와 manager를 사용해서 dictionary를 만들어야 전역으로 사용이 된다고 한다.
게다가 repeat를 사용해서 인자로 주고, 함수와 zip으로 묶어서 pool.starmap의 인자로 넣어준다.

이렇게 딕셔너리로 담아서 json파일로 만들어서 사용할 수도 있다.

 

➕ 추가적인 예제( 2021/04/15 수정 )


다시 사용할 일이 있어서 pool을 이용한 추가 예제를 적어 본다. multiprocessing 은 특히 담으려는 데이터의 순서가 상관없을 때 사용하기 좋은 것 같다. 어떠한 데이터를 불러와서 순서 상관없이 리스트에 담기만 할 것이라면, 이것만 생각하자.

🙋 각 프로세서에게 해야할 일(실행함수) 알려주고, 필요한 재료(파라미터) 손에 쥐어주기!

코드부터 보는게 낫겠지? 예를 들어 문장 데이터를 가져와 명사만 추출해서 리스트에 담는다고 가정해보자.

from multiprocessing import Pool, Manager
from konlpy.tag import Okt


def split_str(str,okt,result_list):
    nouns=okt.nouns(str)
    result_list.append(nouns)

if __name__=='__main__':
      corpus=[                    # 문장 100개가 들어있다 가정하자.
      ['안녕하세요 파이썬 너무 어려워요']
      ['에이 무슨 소리에요']
      ['파이썬이 얼마나 쉬운데']
      ['사실 진짜 어려움']
      ...
      ['그래도 포기하지 말고 해보자']
      ] 
    okt=Okt()                    # Okt 전처리 라이브러리 객체 생성

    process = multiprocessing.cpu_count())     # 프로세서 개수 확인

    m = Manager()                # 데이터 공유를 위한 Manager 객체 생성
    result=m.list()            # 결과 리스트 생성

    pool = Pool(process)        # default 값은 4. 4가 적당한 거 같다.

    # 가장 중요한 실행 부분. starmap의 첫번째 인자로 실행 함수를 주고, 
    # 두번째 인자로 실행 함수의 파라미터를 리스트에서 하나씩 꺼내 튜플로 담아서 하나씩 넘긴다 
    pool.starmap(split_str, [(str, okt, result) for str in corpus])

    pool.close()                # 끝나면 닫아주고
    pool.join()                # 다른 프로세서들이 끝날 때까지 기다린다.

    print(result)                # 결과 확인

 

마무리


가뜩에나 시간이 부족한 우리에게 딱 필요한 기술이지 않나 싶다. 컴퓨터가 신나게 일하도록 해주자~~ 깊게 공부하진 못해서 아쉽긴한데... 언젠가 다시 알게될 날이 있지 않을까..? 그때그때 수정하자 😄

 

reference

'python > Scraping' 카테고리의 다른 글

[Python] 동시성(Concurrency) Asyncio scraping  (0) 2021.08.07
2021-03-22 22:18:15

😫 개요


nginx + gunicorn + flask 조합으로 배포를 해봤었다. supervisor로 모니터링하고 로그를 관리하고 있었다. 그런데 문제는 내가 배포한 플라스크의 모델이 돌아가는데 시간이 길면 1~2분 소모되다보니, 결과값이 나오지 않고 도중에 끊기는 것이다..? 후.. 로컬에선 잘만 돌아가던데.. 🤯 화난 마음을 가라 앉히고, 이럴땐 로그부터 하나씩 살펴보자!

➕ 혹시 supervisor에 더 알고 싶으면 이곳을 참고하자.


🧐 문제 해결 시도


1️⃣ Nginx 로그 확인

제일 먼저 nginx의 err log를 살펴봤다.

# nginx err.log
2021/03/22 20:54:30 [error] 21863#21863: \*335 upstream prematurely closed connection while reading response header from upstream

이 에러가 뜨면서 되지가 않는 것이다. 대강 파파고를 돌려보면 '업스트림에서 응답 헤더를 읽는 동안 업스트림 연결이 조기에 닫힘'이라는데 대충 뭔가 timeout 같다는 느낌이 온다. 그래서 nginx문제인가 싶어서 설정파일에서 아래 부분을 추가했다.

# /etc/nginx/site-enabled/default

...

client_max_body_size 300M;
client_header_timeout 300;
client_body_timeout 300;

...

location / {

    proxy_read_timeout 300s;
    proxy_connect_timeout 300s;

    ...

    }

client_max_body_size는 혹시 몰라서 늘려봤는데, 생각해보니 플라스크 로직 내에서만 큰 용량을 차지하는데, 상관없던 것 같다.
이렇게 설정 파일을 수정한 후에는 꼭 sudo service nginx restart 를 해서 수정한 부분을 적용시켜주자

하지만 여전히 똑같은 현상이 발생하고 있었다... ㅂㄷㅂㄷ 🤬


2️⃣ flask 출력 로그 확인

진짜 뭐지 싶어서 있는 로그가 뭐가 있을까 하다가, 플라스크 실행 로그가 떠올라서 확인해보기로 했다.

# stdout.log
[2021-03-22 20:54:30 +0900] [22129] [CRITICAL] WORKER TIMEOUT (pid:22160)

잡았다 요놈.... 결국 gunicorn의 워커가 timeout이 발생한 것이다. 공식문서에 따르면 default 값으론 30초 였다.
30초론 택도 없지... 길때는 2분까지 걸리는 플라스크에서 돌아가는 연산을 감안하면 이 문제 때문이라고 생각이 된다.

난 supervisor를 이용하니까 .conf에서 세팅을 다시해줬다.

# /etc/supervisor/conf.d/<프로젝트이름>.conf
...
command=<가상환경경로>/gunicorn -b 127.0.0.1:5000 -w 2 -t 240 run:app --preload
...

여기서 -t 240 를 통해 Timeout을 240초로 설정해주는 커맨드다.
자 이제 모든 것이 끝났으니, 적용시켜보자.

sudo supervisorctl restart <program명> 이렇게 해주면 끝인데............. 이걸로만 될 줄 알았는데 .........

여기서 부터 나의 악몽이 시작됐다..😱


3️⃣ 적용이 안되는 삽질

분명 로직상 안될 것이 없는데 계속 똑같은 에러가 발생하는 것이다.


진짜 내가 설정을 잘못했나 싶어서 구글링 엄청하면서 비슷해보이는 코드 적용시켜보고 순서도 바꿔보고, 등등 2시간을 그렇게 삽질했다. 진짜 안되길래 화가나서 worker 수를 바꿔보고 로그 찍어보니 바뀌지가 않는 것이다..? 그래서 로컬에서 돌려보니 로컬에선 되는데 왜 supervisor를 이용하면 안될까? 라는 생각을 하다가 원인을 알아냈다.


그냥 설정 파일의 수정 사항이 적용이 안되는 것이었다.

그래서 supervisor를 reload 해봤다.... 결과는.... 너무 잘되는 걸 😭 (내 2시간..)
sudo supervisorctl reload <program명>


결론 : 여러분들 supervisor .conf 파일 수정후 reload 꼭 하세요... 두 번 하세요 아니 세 번...


🥱 마무리


그래도 이번 경험을 통해 nginx나, gunicorn이 뭔지 살짝 더 알게 되었다. 그리고 꼭 아는 명령어를 맹신하지 말아야겠다는 생각을 가졌다. restart와 같이 정확하게 알지 못하면서 되겠지 라고 생각하기 보다 정확한 쓰임을 알고 사용할 수 있도록 노력해야겠다.

2021-03-20 22:36:53

😆 개요


django로 웹 서비스를 만들어 보면서 비동기 방식을 안 써볼 수가 없다. 그!래!서! 비동기 통신 방식으로 많이 이용하고 있는 jquery의 ajax의 정말 기초적이 사용방법을 알아보려고 한다. 혹시라도 도움이 되는 분들이 있을까봐 포스팅하게 되었다.
(사실 내가 모르고 있어서 안 까먹으려고..)

시작하기 앞서, ajax가 뭘까?라는 생각이 먼저 든다.


Ajax(Asynchornous Javascript And XML)란?

AJAX란, JavaScript의 라이브러리중 하나이며 Asynchronous Javascript And Xml(비동기식 자바스크립트와 xml)의 약자이다. 브라우저가 가지고있는 XMLHttpRequest 객체를 이용해서 전체 페이지를 새로 고치지 않고도 페이지의 일부만을 위한 데이터를 로드하는 기법 이며 JavaScript를 사용한 비동기 통신, 클라이언트와 서버간에 XML 데이터를 주고받는 기술이다.

즉, 쉽게 말하자면 자바스크립트를 통해서 서버에 데이터를 요청하는 것이다.


😎 Vㅔ리 간단한 사용법.


너무 쉬우므로 묻고 코드로 간다.

1️⃣ html 부분

<!-- head 부분 -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

<!-- body 부분 -->
<script type="text/javascript">
$('#submit').on('click', function(e) {
    data = $('#text').val();
    $.ajax({
        type:'POST',
        url:'127.0.0.1:8000/home',
        data:JSON.stringify(data),
        success:function(json){
            console.log("data pass success",json);
        },
        error : function(xhr,errmsg,err) {
        console.log(xhr.status + ": " + xhr.responseText); 
        }
   });
  });


</script>

ajax 부분을 원하는 이벤트 function(위 예제에선 submit click시)안에 넣어주면 된다. 중간중간 나오는 $는 jquery 문법이다.


➕ jquery란?

  • 웹사이트에 자바스크립트를 쉽게 활용할 수 있도록 도와주는 오픈소스 기반의 자바스크립트 라이브러리다.
  • 간단하게 웹페이지 상에서 $ 를 사용해서 Element를 쉽게 찾고 조작할 수 있다.
    • $('#foo') = id가 foo 인 Element
    • $('.foo') = class가 foo 인 Element

➕ data:JSON.stringify(data) 에서 JSON.stringify를 사용하는 이유

  • 요청을 받는 views.py에서 json 파싱을 수월하게 하기 위해 str형태로 만들어서 보냄.

2️⃣ 요청을 받는 views.py 부분

from django.http import JsonResponse

def home(request):
    # POST 요청일 때
    if request.method == 'POST':
        data = json.loads(request.body)
        # do something
        print(data)

        context = {
            'result': data,
        }
        return JsonResponse(context)

home function에서 받은 데이터를 json.loads로 dic로 만들어서 사용할 수 있다. 그렇게 받은 데이터를 요리조리 조리하고, 다시 결과값을 dict형태로 만들어서 JsonResponse를 이용해서 전달한다.


➕ flask 쓸 때처럼 그냥 return json.dumps(context)를 해서 보내면 아래와 같은 에러가 뜬다..

AttributeError: 'str' object has no attribute 'get'
[20/Mar/2021 11:36:08] "POST /home HTTP/1.1" 500 60851

🤗 마무리


아주 기초적인 부분인데 이런 것에 막혀서 시간을 뻈겼던, 쉬운 방법이 있었는데 어렵게 하던 내 자신이 부끄럽긴 하지만… 그래도 이런 기회를 통해 알게 됐다!! 안까먹기 위해서 포스팅을 많이많이 해두자..😢


reference

2021-03-20 21:54:25

😄 개요


간만에 장고로 다시 개인적인 toy project를 해보고 싶어서, 장고 프로젝트 생성하고 간단하게 POST 요청 비동기통신을 이용해보고 싶어서 jquery의 ajax를 사용했다. 역시, ERROR가 안뜰리 없다.


😱 문제점


우선 작성한 코드는 아주 간단했다.

$.ajax({
      type:'POST',
      url:'/search',
      data:JSON.stringify(geodata),
      success:function(json){
          console.log("to view data pass success",json);

      },
      error : function(xhr,errmsg,err) {
      console.log(xhr.status + ": " + xhr.responseText); 
      }
      });

search 로 POST 요청을 보냈는데.......?

Forbidden (CSRF cookie not set.): /search
[20/Mar/2021 10:47:16] "POST /search HTTP/1.1" 403 2864

결과는 처참했다....☠️☠️☠️☠️☠️

간단하게 요약하자면 django의 CSRF(Cross Site Request Forgery 사이트 간 요청 위조) 보안 정책으로 인해 일어난 에러이다.


👍 해결법


1️⃣ decorator를 사용하여 특정 view에 csrf 적용하지 않기

단순하게 csrf 정책을 사용하지 않는 것이다. 방법은 아주 간단하다. views.py에 원하는 view에 decorator를 달아주면 된다.

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def search(request):

    return HttpResponse('success')

이렇게 하면 해결되지만, 뭔가 보안상 뭔가 찜찜하다.. 그래서 다음 방법을 준비했다❗️


2️⃣ html 파일 header부분에 csrf token 생성하기

이 방법은 django 공식 문서(보러가기)에 있는 방법이다. ajax를 사용하는 html 파일의 header 부분에 아래 코드를 작성하면 된다.

<script>
  function getCookie(name) {
      var cookieValue = null;
      if (document.cookie && document.cookie !== '') {
          var cookies = document.cookie.split(';');
          for (var i = 0; i < cookies.length; i++) {
              var cookie = cookies[i].trim();
              // Does this cookie string begin with the name we want?
              if (cookie.substring(0, name.length + 1) === (name + '=')) {
                  cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                  break;
              }
          }
      }
      return cookieValue;
  }
  var csrftoken = getCookie('csrftoken');

  function csrfSafeMethod(method) {
      // these HTTP methods do not require CSRF protection
      return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
  }
  $.ajaxSetup({
      beforeSend: function(xhr, settings) {
          if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
              xhr.setRequestHeader("X-CSRFToken", csrftoken);
          }
      }
  });
</script>

대강 보면 토큰을 만들고 ajax에 미리 설정해주는 것 같다. 필요한 사항은 당연히 jquery를 먼저 불러와야 한다.


➕ 2021/04/08 내용 추가

후에 위의 방법으로도 됐다가 안 됐다가 하다가 결국 또 안되길래 간단한 다른 방법을 찾았다.

<script>
  $.ajaxSetup({
    headers: { "X-CSRFToken": '{{csrf_token}}' }
  });
</script>

단순히 이 부분만 html 파일의 header 부분에 추가해주면된다... 이것 때문에 시간 또 날린거 너무 화나네🤬


😂 마무리


처음 배울 때에는 어떤 것인지도 모르고 단순히 에러만 없애려고 사용했었는데, 다시 보니 감회가 새롭고 오히려 더 기억에 남게 되었던 것 같다. 하지만 내 머리를 너무 믿지 말자. 오늘도 포스팅... 포스팅...🧑‍💻


reference

'python > django' 카테고리의 다른 글

[django] mysql + docker-compose  (1) 2022.12.25
[django] ajax를 이용해서 데이터 주고받기  (0) 2021.03.20
2021-03-16 18:49:36

개요


정규표현식으로 자연어 처리를 할 때에 있던 일이다. 분명 한글인데, 인식을 못하는 경우가 있다. 인코딩 문제가 아닐까 싶다. 정확한 원인 규명은 못했지만 내 나름대로 해결한 문제를 기록해본다.


문제점


내가 정규표현식을 통해 한글을 검색하려 했었는데, None을 뱉어내는 경우가 있어서 이상하게 여겨 한번 테스트를 해봤다.

subs=[
  '대법원 2016. 10. 13. 선고 2016두42449 판결',
  '대법원 2017. 11. 23. 선고 2015다1017, 1024, 1031, 1048 판결'
  ]

com=re.compile(r"[가-힣]")
for sub in subs:
  search_word=com.search(sub)
  print(f'{sub}에서 검색 결과 : {search_word}')

# 출력 결과
# 대법원 2016. 10. 13. 선고 2016두42449 판결에서 검색 결과 : None
# 대법원 2017. 11. 23. 선고 2015다1017, 1024, 1031, 1048 판결에서 검색 결과 : <re.Match object; span=(0, 3), match='대법원'>

??? 이게 무슨 일이지? subs 안에 들어있는 두 문장은 영락없는 한글인데 하나는 되고, 다른 하나는 왜 안될까?
나는 내 정규식이 틀렸나 싶어서 https://regexr.com/ 이 사이트에서 확인을 해보았다.

??? 왜 또 안될까? 어이가 없어서 저 안되는 부분을 똑같이 복붙해봤다.

이번엔 위에껀 잡히는데 밑에껀 또 안잡힌다;;;; 뭔 차이가 있을까 싶어서 밑에 결과를 살펴보니 알 수 있었다.

뭔 차이인지 알겠는가? 보시다시피 code가 다른 것을 알 수 있다! 인코딩이 잘못된 것 같다는 생각이 들었다...

그리고 마우스를 가져다 대면,

저렇게 나뉘어지는 것을 보고 인코딩이 잘못되었다는 것을 확신할 수 있다.

# 원본 텍스트
original='대법원 2016. 10. 13. 선고 2016두42449 판결'
# 그대로 직접 타이핑한 텍스트
copy='대법원 2016. 10. 13. 선고 2016두42449 판결'

print(len(original),len(copy))

# 출력 결과
# 47, 34

심지어 길이도 달랐다. 그러니 내가 단어를 찾을 수가 없었던 것이었다... 이런걸 어떻게 사용해야할까...


내가 생각한 해결법


어차피 정규식도 유니코드로 찾아내는 거니까 코드 번호로 명시해주면 찾아지지 않을까? 라는 생각을 했다. 그래서 실제로 시도한 결과..

잘 잡혔다! 대충 어림잡아 4200~4800사이로 바꿔서 \u(16진수) 이와 같이 넣어주었다.


21/04/01 업데이트 내용.


이후에 업무를 하다가, 윈도우에서 만든 텍스트 파일을 mac에서 사용하려니 문제가 생겼다. cp949로 인코딩해서 내용물을 활용한 것 까지는 좋았다. 하지만 내가 코드 중에, 파일 제목을 변수에 담아서 내용과 비교를 하는 로직을 짠 적이 있었다. 또 찾을 수 없다고 뜨길래 확인해보니 위의 상황들과 같았다.

import glob

file_list=glob.glob('./data/*.txt')

for file in file_list:
    title=file.split('/')[2]
    load_file(title)

    ...

# 결과    
KeyError: 'title'

오잉? 분명 있는 것인데 왜 없다고 하지??? 그래도 이번에 문제점은 확실히 알았다(사실 몇시간 끙끙 앓았음...). 내용은 파이썬 내부함수로 파일을 읽으면서 인코딩하면 되지만, 파일 제목은 인코딩이 되지 않는다는 점이다. 그래서 이것을 어떻게 다시 돌려놓았는가 함은...!!!

from unicodedata import normalize
import glob

# 여기서 파일 명을 들고오면서 인코딩이 안된 것을 가지고 사용하다보니 에러가 난것이다..
file_list=glob.glob('./data/*.txt')


for file in file_list:
    title=file.split('/')[2]
    title = normalize('NFC', title)
    load_file(title)

    ...

from unicodedata import normalize 를 임포트 해와서!!
title = normalize('NFC', title) NFC 로 맹글어주면 된닷 ㅎㅎ

정말 간단했다.. 코드 몇줄로 끝나다니 살짝 허무하긴 했는데, 해결되서 다행이다 😩
여기서 NFC가 뭔지 궁금하신 분들은 이 곳 에서 설명이 잘 되있으니 참고하시면 될 것 같다!


마무리


DB에서 데이터를 가져오다보니 어쩌다 이렇게까지 하게 되었는지 모르겠지만, 원인이 정확히 어디서부터 그렇게 된지 찾기가 쉽지가 않다. window와 mac 사이를 파일로 왔다갔다해서 그런가 인코딩이 엉킨거 같기도 하다. 어떻게 통일화할지는 아직 생각을 못해봤는데, 그것도 언젠가 찾아서 하게 되면 안까먹게 포스팅 해야겠다 😋

reference

2021-03-14 20:25:50

🙌 개요


AWS EC2를 이용해 배포를 해보고 싶은 생각이 들었다. 그래서 제일 먼저 다음과 같이 했다.

  1. 프리티어로 EC2 ubuntu 20.04 로 인스턴스 생성하고 pem 키 발급
  2. ~/.ssh 폴더 를 만들고, key를 안으로 옮겨서 chmod 400 (key이름).pemssh -i ubuntu@퍼블릭주소 .ssh/(key이름).pem
  3. ubuntu 환경으로 들어와서 사용자 암호 부여를 하라해서 sudo passwd 먼저해서 암호부터 설정

이후로는 뭐부터 해야할지 모르겠다... python3 -V 한 결과 python 3.8 이었다. 난 3.7.9 쓰는데,,, 파이썬 버전부터 바꿔보기로 했다.

(사실 다음에 할 때 검색해서 알아놓은 것들 까먹을까봐 기록한다 😋)

📃 설치


나는 EC2 ubuntu 20.04 이고, bash를 사용했다.

 

1️⃣ 제일 먼저 pyenv를 다운 받기 전에 패키지 정보를 업데이트 한다.

sudo apt-get update
sudo apt-get dist-upgrade
sudo apt-get install python3-pip

 

2️⃣ pyenv를 설치하기 전에 requirements 설치를 하고 git 으로 pyenv 설치한다.

sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils

git clone https://github.com/pyenv/pyenv.git ~/.pyenv

 

3️⃣ 그리고 shell 설정 파일에 환경변수를 설정해준다.

echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc

eval "$(pyenv init -)"
# 또는 shell config file에 등록
echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n  eval "$(pyenv init -)"\nfi' >> ~/.bashrc

 

4️⃣ 그 다음, 설정 사항을 적용시키기 위해 exec "$SHELL" 명령어를 통해 shell을 restart 해준다.

 

5️⃣ 마지막! 기다리던 파이썬 설치.

# 설치가능 버전 확인
pyenv install --list

# 3.7.9 버전 설치
pyenv install 3.7.9

# 설치된 버전 확인
pyenv versions 

# 결과
* system
 3.7.9 

# pyenv global 3.7.9

# 결과
 system
* 3.7.9 

 

👏 마무리


시작이 반이라고 이제 벌써 반 정도 한 것 같이 힘들다.. 앞으로 django nginx gunicorn 을 이용한 배포를 꼭 성공시켜보자..🔥

 

reference