작성 주식 뉴스 분석 프로젝트 <news crawl><4>
프로젝트 개요 및 각 글들의 링크: https://namth.tistory.com/18
핵심적인 로직들을 작성하였으므로 이제 http 요청에 응답 가능하게 웹 서버를 작성하고,
배포를 위해 dockerfile을 작성할 것이다.
웹 서버
웹 서버 프레임워크는 Flask를 사용하여 작성하였다.
구동은 gunicorn으로 구동할 예정이다.
POST body의 데이터에서 크롤링할 주제의 이름과 언어의 코드를 가져온다. (subject, source_lang)
라우팅 되는 함수의 데코레이터 중 abstract_request 함수가 있는데, 원래는 cors 및 jwt토큰 검사를 위해 request객체를 추출하는
함수였지만, 여기서는 제대로 사용하지 않고 있다. (modules/req_valid.py)
# https://github.com/mannamman/newsCrawl/blob/main/main.py | |
from modules.newsCrawler import HeaderCrawler | |
from modules.mongo_db import DBworker | |
from modules.file_worker import FileWorker | |
from modules.log_module import Logger | |
from finBERT.sentiment import FinBert | |
import pytz | |
import datetime | |
import functools | |
from math import ceil | |
import os | |
import traceback | |
import copy | |
from typing import Tuple, List, Dict | |
from uuid import uuid4 | |
from flask import Response, Request | |
import json | |
# multi process | |
from multiprocessing import Pool | |
# server | |
from flask import request, Flask | |
app = Flask(__name__) | |
# 객체 초기화(공통으로 사용되는) | |
db_worker = DBworker() | |
file_worker = FileWorker() | |
sentiment_finbert = None | |
logger = Logger() | |
KST = pytz.timezone("Asia/Seoul") | |
def init_sentiment(): | |
global sentiment_finbert | |
sentiment_finbert = FinBert() | |
def abstract_request(func): | |
@functools.wraps(func) | |
def wrapper(*args, **kwargs): | |
return func(request) | |
return wrapper | |
@app.route("/sentiment", methods=["POST"]) | |
@abstract_request | |
def index(req: Request): | |
global KST | |
global logger | |
global sentiment_finbert | |
global db_worker | |
# lazy loading | |
if(sentiment_finbert is None): | |
init_sentiment() | |
try: | |
# post 메시지 파싱 | |
recevied_msg = json.loads(req.get_data().decode("utf-8")) | |
subject = recevied_msg["subject"] | |
source_lang = recevied_msg["source_lang"] | |
... | |
return Response(response="ok", status=200) | |
except Exception: | |
error = traceback.format_exc() | |
error = pretty_trackback(error) | |
logger.error_log(error) | |
return Response(response=error, status=400) | |
if(__name__ == "__main__"): | |
app.run(host="0.0.0.0", port=8080, debug=False) |
Dokcerfile 작성
GCP cloud run으로 배포하기 위하여 웹서버를 docker image로 만들어야 했다.
GCP cloud run을 사용한 이유는 우선 회사에서 사용을 자주 해봐서 익숙하고,
사용한 만큼 요금이 부과되어서 요금이 적게 든다. 또한 요청이 줄거나 증가하면 인스턴스(서버)의 개수를 조정하여 부하를 맞춰준다.
FROM python:3.8.12-slim-buster | |
LABEL maintainer="wase894@gmail.com" | |
ENV TRANSFORMERS_OFFLINE 1 | |
EXPOSE 8080 | |
WORKDIR /app | |
COPY . /app | |
RUN apt update && apt install -y curl | |
RUN pip3 install -r requirements.txt | |
RUN python3 -m nltk.downloader punkt -d /usr/local/nltk_data | |
RUN python3 -m nltk.downloader stopwords -d /usr/local/nltk_data | |
# 개인 스토리지(public) | |
RUN curl https://storage.googleapis.com/public-model-bucket/pytorch_model_fin.bin --output /app/finBERT/models/sentiment/pytorch_model.bin | |
CMD exec gunicorn --bind 0.0.0.0:8080 --workers 2 --threads 1 --timeout 240 main:app |
실행에 필요한 모듈 목록은 requirements.txt에 있으므로 해당 파일 읽어 모듈을 설치하고
nltk의 경우 따로 데이터가 더 필요하여 추가적으로 데이터를 다운로드하였다.
모델의 경우 다운로드를 위해 스토리지에 업로드 후 객체를 공개로 전환하여 다운로드가 가능하게 만들었다.
Flask와 gunicorn을 사용해야 성능이 제대로 나오므로 gunicorn을 이용하여서 서버를 실행시켰다.


참고자료
알게 된 점 기록
- 보통 gunicorn을 같이 사용하는 이유: https://blog.ironboundsoftware.com/2016/06/27/faster-flask-need-gunicorn/
- ngix + gunicorn + flask: https://serverfault.com/questions/220046/why-is-setting-nginx-as-a-reverse-proxy-a-good-idea
- gunicorn으로 실행 시 실행파일(main.py)의 if(__name__=='__main__') 조건 문안의 코드가 실행이 안됨
https://stackoverflow.com/questions/67084165/does-gunicorn-also-execute-if-name-main
# 2022.09.30 수정
'프로젝트 > 주식뉴스분석' 카테고리의 다른 글
AWS EC2 인스턴스 생성 및 접속하기 (2) | 2022.09.26 |
---|---|
구글 클라우드 빌드(GCP build + GitHub)을 사용한 지속적 배포(CD) (0) | 2022.09.26 |
결과 저장(GCP Storage, MongoDB) (0) | 2022.09.21 |
뉴스 제목 감정분석(sentiment analysis) (0) | 2022.09.20 |
구글 뉴스 제목 크롤링 (0) | 2022.09.20 |