[250901월2042H] 영화 리뷰 AI 분석 웹앱 구현 (1)

윤승호·2025년 9월 1일

시간이 참 빠르다. 벌써 마지막 미션이라니,,, 어렵지만 끝까지 힘내보자!!

학습시간 09:00~03:00(당일18H/2042H)


◆ 학습내용

영화 리뷰 AI 분석 웹앱 구현하기!!!

프론트엔드 (Streamlit)

  • 영화 목록 표시 기능 = 제목, 포스터 이미지, 평균 평점 표시
  • 영화 추가 기능 = 입력: 제목, 개봉일, 감독, 장르, 포스터 URL
  • 리뷰 등록 기능 = 저장된 영화 선택, 작성자 이름, 리뷰 내용 입력
  • 리뷰 감성 분석 기능 = 리뷰 작성 후 자동 실행, 감성 분석 결과 표시
  • 리뷰 표시 기능 = 최근 10개 리뷰 표시, 항목: 영화 ID, 등록일, 리뷰 내용, 감성 분석 결과

백엔드 (FastAPI)

  • 영화 관리 기능 = 등록: 제목, 개봉일, 감독, 장르, 포스터 URL(나무위키 참고), 전체/특정 영화 조회, 특정 영화 삭제)
  • 리뷰 관리 기능 = 등록, 전체/특정 영화 리뷰 조회, 삭제
  • 평점 조회 기능 = 리뷰 감성 분석 점수의 평균
  • 리뷰 감성 분석 기능 = 적절한 모델 리서치하여 적용(경량화)

참고사항

  • 모든 데이터는 백엔드에서 관리(Streamlit 내부에서 별도 저장 기능 사용 안 함)
  • frontend, backend 폴더로 구분하여 저장

1. 계획

마지막 미션이라 그런지 구현해야 할 기능이 많다. 모델까지 직접 만드는 건 아니라서 시간적 여유가 있을 것 같지만, 그래도 프론트엔드 구조와 백엔드 코드를 동시에 짜다 보면 빠듯하긴 할 것 같다...

(1) 프론트엔드

일단 프론트엔드 구조를 어떻게 짤지 생각해 보자.

메인 화면에는 영화 목록이 보이고, 각 영화마다 제목&이미지&평점이 함께 나타나야 한다.

롯데시네마 홈페이지에 들어가니 괜찮은 래퍼런스가 있다. 포스터를 중심으로한 구조로 가되, 한 열에 5개씩 나타나도록 하면 될 것 같다.

상단에는 영화 추가 버튼을 만들어서 버튼을 누르면 제목, 개봉일, 감독, 장르, 포스터 URL를 넣을 수 있는 팝업창이 뜨도록 하자. 삭제도 있어야 겠군

네이버의 영화 감상평 화면이다. 리뷰 쪽도 이걸 참고하면 될 것 같다. 메인 화면의 영화 포스터를 클릭하면 리뷰를 확인할 수 있는 창으로 이동되고, 별다른 회원가입 없이 이름과 내용만으로 리뷰 등록이 가능하게 하자.

내용 수정을 해야할 수도 있으니 리뷰 화면에 수정 버튼을 만들어서 관리자 비밀번호 입력 후 수정할 수 있게 만들자.

(2) 백엔드

어떤 API를 만들어야 할지 미리 생각해 보자.

메인 화면:

  • 영화 기본 정보 조회
  • 영화 등록

영화 화면(상단):

  • 영화 세부 정보 조회
  • 영화 세부 정보 수정
  • 영화 삭제

영화 화면(하단):

  • 리뷰 조회
  • 리뷰 등록
  • 리뷰 수정
  • 리뷰 삭제

내 생각대로면 9개의 API가 필요하다. 이걸 기본으로 하되 진행하면서 가감을 해보자.

(3) 모델

리뷰를 분석 후 점수를 매길 모델을 찾아야 한다.

오랜만에 허깅페이스 쇼핑좀 해볼까!!

텍스트 분류 테스크와 한국어 옵션을 선택해 주자.

tabularisai/multilingual-sentiment-analysis 모델이 돋보적인 것 같다.

135M개의 파라미터, 텐서타입은 F32다.

용량도 작고 좋다.

DistilBERT를 개량한 모델이라고 한다.

DistilBERT: Distilling Knowledge기법을 기존의 BERT model에 적용해 훨씬 작은 크기, 빠른 속도를 가지면서 비슷한 성능을 보이는 모델

LLM이 생성한 데이터셋으로 학습을 시켰다고 하니,, 성능적으로 약간 미흡한 부분도 있을 것 같다.

일단 이 모델로 결정!!


2. 프론트엔드

def get_dummy_movies():
    return [
        {'id': 1, 'title': '범죄도시4', 'poster_url': 'https://movie-phinf.pstatic.net/20240424_184/1713943856025fh3Qa_JPEG/movie_image.jpg'},
        {'id': 2, 'title': '극장판 귀멸의 칼날: 무한성편', 'poster_url': 'https://movie-phinf.pstatic.net/20250822_113/17558482824812SGWC_JPEG/movie_image.jpg'},
    ]

def get_dummy_reviews_for_movie(movie_id):
    if movie_id == 1: # '범죄도시4'에 대한 리뷰
        return [
            {'id': 101, 'author': '마동석', 'review_text': '진실의 방으로.', 'sentiment_result': '긍정', 'created_at': '2024-04-24T10:00:00'},
            {'id': 102, 'author': '김무열', 'review_text': '전투력이 너무 강력하다...', 'sentiment_result': '부정', 'created_at': '2024-04-25T14:30:00'},
        ]
    return []

현재 백엔드와 DB 파일이 없으니 더미 데이터를 생성해 준다.

if 'view' not in st.session_state:
    st.session_state.view = 'main'
    st.session_state.selected_movie_id = None
    st.session_state.selected_movie_title = None

Streamlit 세션을 만들어서 저장해 준다.

# 메인 영화 목록 화면
def render_main_page():
    st.title("🎬 Movie Review")
    
    # 영화 추가 버튼 로직
    if "show_add_movie_form" not in st.session_state:
        st.session_state.show_add_movie_form = False

    if st.button("Add Movie", use_container_width=False):
        st.session_state.show_add_movie_form = True

메인 화면에 영화 추가 버튼을 만든다

    st.divider()
    
    movies = get_dummy_movies()
    num_movies = len(movies)
    num_cols = 5
    
    cols = st.columns(num_cols)
    for i in range(num_movies):
        movie = movies[i]
        with cols[i]:
            with st.container(border=True):
                st.image(movie['poster_url'])
                st.caption(movie['title'])
                
                rating = 4.88
                st.markdown(f"⭐ **{rating:.2f}** / 5.0")
                
                if st.button("리뷰 보기", key=f"review_{movie['id']}", use_container_width=True):
                    st.session_state.view = 'review'
                    st.session_state.selected_movie_id = movie['id']
                    st.session_state.selected_movie_title = movie['title']
                    st.rerun()

버튼 아래 영화 포스터 목록을 만든다. 아까 만든 더미데이터 함수를 사용한다.

잘 나온다.

(마동석은 아카자를 쉽게 이길 수 있지 않을까?)

    if st.session_state.show_add_movie_form:
        with st.container(border=True):
            st.subheader("새 영화 추가")
            with st.form("add_movie_form"):
                st.text_input("제목")
                st.date_input("개봉일")
                st.text_input("감독")
                st.text_input("장르")
                st.text_input("포스터 URL")
                
                col1, col2 = st.columns([1, 5])
                with col1:
                    submitted = st.form_submit_button("추가하기")
                    if submitted:
                        st.session_state.show_add_movie_form = False
                        st.rerun()
                with col2:
                    if st.form_submit_button("닫기", type="secondary"):
                        st.session_state.show_add_movie_form = False
                        st.rerun()

영화 추가 버튼이 눌렀을 때 작동할 로직이다.

이것도 잘 나온다! 아직 뭐가 없어서 추가하기를 눌러도 반응이 없다.

이제 리뷰 페이지를 만들어 보자.

요기서 리뷰 보기 버튼을 누르면 넘어갈 페이지를 구현해야 한다.

def render_review_page():
    movie_id = st.session_state.selected_movie_id
    current_movie = next((m for m in st.session_state.movies if m['id'] == movie_id), None)
    
    # 페이지 제목
    st.title(f"[ {current_movie['title']} ] Movie Review")

    # 홈 버튼
    if st.button("Home", use_container_width=False):
        st.session_state.view = 'main'
        st.rerun()

    # 리뷰 작성 폼
    st.subheader("Add Review")
    with st.form("add_review_form", clear_on_submit=True):        
        st.text_input("Name")
        st.text_area("Content")
        if st.form_submit_button("Submit"):
            st.toast("OK")

페이지 제목, 홈 버튼, 리뷰 작성 폼을 차례대로 만든다.

잘 생성됐다.

이제 사람들이 작성한 리뷰가 나오도록 해야한다.

선을 하나 그려 리뷰 작성 컨테이너와 구분을 해주고, 그 아래 리뷰가 나오게 해보자.

    st.divider()

    # 리뷰 목록
    reviews = get_dummy_reviews_for_movie(movie_id)
    for review in reviews:
        with st.container(border=True):
            st.markdown(f"**{review['author']}**")
            st.write(review['review_text'])
            st.info(f"AI 분석 평점: 4.88")
            st.button("Delete", key=f"delete_review_{review['id']}")

더미데이터로 만들어놨던 데이터가 잘 출력되었다.

내일은 여기에 백엔드와 SQL을 연결해봐야지...!

profile
나는 AI 엔지니어가 된다.

0개의 댓글