Skip to content

cafe-snap/cafesnap-client

Repository files navigation

CAFESNAP은 사용자가 가입한 네이버 카페의 게시글 중 동영상을 추출하여 보여주는 숏츠 플랫폼입니다.
클라이언트 | 서버

🗂 목차


💭 프로젝트 동기

여가 시간을 좀 더 편리하게 즐기고자 하는 생각에서부터 출발한 프로젝트입니다.
좋아하는 축구팀의 소식, 여러 여행지의 정보 등 저는 네이버 카페를 통하여 다양한 정보를 얻고, 또 많은 시간을 소비합니다. 특히, 축구팀의 경기가 있는 날 많은 양의 글이 올라오고 그 속에서 영상만 찾아보는 것이 꽤 불편하게 느껴져 이러한 불편함을 해소하고 싶었습니다.

이미 유튜브 숏츠, 인스타 릴스, 틱톡 등 다양한 숏츠 플랫폼이 존재하지만, 네이버 카페라는 폐쇄적인 특수성을 갖는 플랫폼은 마주한 적이 없었고, 나의 필요에 의한 것이니 더욱 흥미있게 또 그 속에 도전적인 요소가 많겠다고 생각 하며 프로젝트를 시작했습니다.


🛠 사용 기술

프론트엔드

JAVASCRIPT TYPESCRIPT REACT Zustand TAILWINDCSS VITE

백엔드

NODE.JS EXPRESS.JS PUPPETEER


🎞 구현 기능 미리보기

카페별 동영상 시청 동영상 검색 기능

📝 구현 세부사항

크롤링을 사용한 필수 데이터 확보

크롤링(Crawling) = 웹 페이지의 데이터를 자동으로 가져오는 기술

이 프로젝트에서 가장 중요한 데이터는 사용자가 가입한 카페의 목록과 동영상 2가지입니다.
때문에, 이 2가지 데이터를 가져오는 방법에 대한 고민을 가장 먼저 시작했고 크롤링을 사용해 데이터를 가져왔습니다.

1. 네이버 API의 제한사항 : Puppeteer를 사용한 해결

네이버 API 목록

네이버 카페의 게시글을 기반으로 하는 프로젝트인 만큼, 최우선 순위로 고려한 방법은 네이버 API를 사용하는 것이었습니다. 하지만 여러 제한 사항에 의해 네이버 API를 사용한다면 이 프로젝트를 완성할 수 없다고 판단했습니다.


1-1. 제공하지 않는 서비스

사용자가 가입한 카페의 목록을 제공하지 않습니다. 동영상 역시 마찬가지로, 동영상 자체를 제공해 주는 API는 없습니다. 카페 게시글의 동영상을 얻고 싶다면 다음과 같은 순서를 따라야 합니다.

   1. 검색 API를 사용하여 특정 키워드에 대한 검색을 진행합니다.
   2. 해당 결과와 일치하는 반환값을 받습니다.
   3. 반환된 여러 값들중 카페 게시글 URL 값을 찾아냅니다.
   4. 해당 게시글 URL에 접속하여 DOM 요소를 통해 동영상 포함 여부를 확인합니다.
   5. 크롤링 로직을 통해 동영상 데이터를 추출합니다.

이처럼 직접적으로 카페의 동영상 데이터를 제공해 주는 API는 존재하지 않습니다.


1-2. 데이터의 유효성 부족

API와 크롤링의 특징을 비교할 때 가장 흔하게 마주할 수 있는 특징은 데이터의 신뢰성 입니다. 공식적으로 제공되는 데이터이기 때문에 API가 크롤링에 비해 신뢰성이 높다는 것이 일반적인 의견입니다. 이러한 특징에 더하여 직접적으로 원하는 데이터를 제공 받을수는 없지만, 검색 API를 사용한다면 간접적으로 데이터를 얻을수 있다라는 생각에 검색 API를 통해 얻는 데이터의 유효성을 판단했습니다.

제가 생각한 유효성의 기준은 받아오는 데이터 중 사용 가능한 게시글의 개수, 다시 말해 동영상이 포함되어 있으며 내가 가입한 카페일 확률입니다.

네이버가 제공하는 검색 API는 검색 키워드 1개에 대하여 최대 1000개의 카페 게시글 주소를 반환합니다.
검색 결과가 일치할 경우 게시글의 공개, 비공개 여부를 상관하지 않고 결과를 반환합니다.
다음은 위 조건을 토대로 진행한 실험의 결과입니다.

실험 결과 일부

각기 다른 검색 키워드를 통하여 여러 차례 실험해 봤지만, 결과의 수치는 크게 다르지 않았습니다. 반환되는 게시글 중 동영상을 포함한 게시글의 수는 100개를 기준으로 0개였으며, 5회 진행 시 1회 정도의 확률로 1~2개의 동영상을 포함한 게시글을 확인할 수 있었습니다.
이마저도 단순히 동영상을 포함한 게시글일 확률이며, 이 게시글이 내가 가입한 카페의 게시글일 확률은 0%에 수렴하고, 이 결과는 제가 생각한 데이터의 유효성 기준에 너무나 벗어난 수치였습니다.


1-3. 크롤링의 사용 이유

네이버 API를 사용하면 프로젝트에 필요한 데이터를 얻을 수 없는 제한 사항이 존재했습니다.
크롤링을 사용한다면 이런 제한 사항을 극복하고 프로젝트에 필요한 데이터를 직접적으로 확보할 수 있었습니다.

  1. 사용자 가입 카페 목록
    네이버 API로는 제공받을 수 없는 사용자 가입 카페 목록을, 크롤링을 통해 얻을 수 있게 됩니다. 사용자가 가입한 카페 목록은 물론이고, 이 목록을 활용해 해당 카페들의 게시글에 접근할 수 있습니다.

  2. 데이터의 유효성 한계 극복
    검색 API를 사용할 경우, 반환되는 데이터의 대부분이 게시글에 접근조차 할 수 없는 비공개 게시글이거나, 동영상이 포함되지 않는 유효성이 없는 데이터들이었습니다. 크롤링을 사용한다면 이러한 유효성을 프로젝트의 필요성에 맞게 조정할 수 있습니다.


2. 동영상 데이터를 크롤링하는 방법

크롤링 로직 작성을 통하여 사용자가 가입한 카페 목록을 확보할 수 있게 됐습니다. 그다음 단계는 해당 목록을 바탕으로 카페별 동영상 미디어를 확보해야 했습니다.

2-1. 게시글의 동영상 포함 여부 확인

동영상을 얻기 위해서는 게시글에 동영상이 포함되어 있는지 확인하는 것이 첫 번째 단계라 생각했습니다. 이때, 모든 게시글 내부에 접근하여 포함 여부를 판단하는 것은 비효율적이라 판단했고, 내부에 접근하지 않고 동영상의 포함 여부를 확인하는 방법을 찾았습니다.


해당 사진은 특정 카페의 전체 게시글 목록을 캡쳐한 사진입니다. 빨간색 동그라미 친 부분의 아이콘은 해당 게시글에 동영상이 포함되어 있음을 알려주는 아이콘입니다. 이 아이콘을 통해서 직접 게시글 내부에 접근하지 않아도 동영상의 포함 여부를 판단할 수 있게 되고, 해당 아이콘의 DOM 요소인 span 태그의 class 속성 list-i-movie 를 사용하여 전체 게시글 페이지만을 이동하며 동영상 포함 여부를 판단할 수 있었습니다.


2-2. 예상하지 못한 크롤링 결과 데이터

게시글 내부에 접근하지 않고, 동영상의 포함 여부를 확인하는 방법을 찾은 이후 크롤링 로직을 구현하여 결과를 확인했지만 예상하지 못한 결과를 확인했습니다.
분명히 로그인을 완료하고, 컨텐츠의 로딩을 기다린 후 page.content() 메서드를 사용하여 본문 크롤링을 시도했지만, 기대했던 결과와 전혀 다른 결과가 반환됐습니다. 이유는 네이버 카페 게시글 페이지의 iframe DOM 구조 때문이었습니다.
iframe은 부모 페이지와 독립적인 DOM 트리를 형성합니다. 다시 한번 개발자 도구를 확인하여 크롤링하고자 했던 DOM의 최상위 요소를 확인해 보니 iframe 태그로 감싸져 있는 걸 확인 할 수 있었습니다.


이를 해결 하기 위해 iframe 요소를 선택한 후, contentFrame() 메서드를 사용해 iframe 내부 DOM 컨텍스트를 확보하여 원하는 크롤링 데이터를 얻을 수 있게 되었습니다.


2-3. 게시글로부터 동영상 데이터 확보

동영상이 포함된 게시글 주소를 확보한 이후, 동영상 데이터를 가져오기 위한 로직을 구현하기 시작했습니다.
이를 위해 게시글 내부 video 태그의 src 속성을 확인했습니다.


당연하게 존재할 것이라 생각한 video 태그의 src 속성은 존재하지 않았습니다. 해당 속성은 게시글 접근 초기에는 존재하지 않다가 동영상의 재생버튼을 클릭한 이후, 동적으로 생성되었습니다.


이러한 구조라면 게시글에 접근하여 재생 버튼을 클릭한 다음, 다시 DOM 요소를 불러와야 하는 흐름이었고, 이 한 단계의 추가적인 행동이 동영상이 포함된 모든 게시글마다 실행된다면 크롤링 속도 저하에 영향을 끼친다고 판단했습니다.

해결하기 위한 방법을 찾던 중, 동영상 재생 시 스트리밍되는 데이터와 별개로 네트워크 요청에서 초기 데이터를 받아오는 경우가 있다는 정보를 얻었습니다. 실제로 네트워크 요청 탭을 확인해 보니, 수많은 요청 주소 중 해당 게시글의 동영상 데이터를 갖는 응답을 확인할 수 있었습니다.


게시글 접속 시 발생하는 수많은 네트워크 요청 가운데, 동영상 데이터를 갖는 응답은 다른 요청들과는 다르게 유일한 주소 패턴을 갖고 있었습니다.

이러한 특징 덕분에 모든 네트워크 요청을 일일이 확인할 필요 없이 조건문 하나로 빠르게 필요한 네트워크 요청을 확인 할 수 있었고, 동영상 데이터를 가져올 수 있게 되었습니다.


Zustand를 사용한 API 요청 과 응답 데이터의 관리

이 프로젝트에서는 필요한 데이터의 종류에 따라 서로 다른 API 요청을 서버에 전송합니다. 초기 개발 시에는 대부분의 기능을 메인페이지에서 구성하고, 몇 가지 모달을 사용한 UI를 구성했기에 전역 상태 관리를 사용하지 않은 로직을 작성했습니다. 그러나 프로젝트를 진행함에 따라 전역 상태 관리의 필요성을 느끼기 시작했습니다.

1. 길어지는 메인페이지의 코드 : 컴포넌트의 관심사 분리

대부분의 기능이 실행되는 메인페이지 코드의 길이는 프로젝트가 진행됨에 따라 점점 길어졌습니다. 늘어난 코드의 길이를 줄여 유지보수성을 향상하고 싶다는 생각, 메인 페이지는 동영상 재생 및 조작의 기능만 담당하는 관심사의 분리를 실현해 보고 싶다는 생각으로 API 요청 함수를 별도 파일로 분리하여 관리했습니다.
하지만, 이 방식 역시 개선이 필요해 보였습니다.

전역 상태 도입 전후 메인페이지 길이 비교
로직 분리 전 로직 분리 후

분리 전후 약 100여줄 이상의 차이


1-1. 다양한 API 요청 시점의 자동화

다양한 API 요청 함수의 동작을 매번 명시적인 사용자 상호작용을 통하여 구현한다면 사용자 경험이 저하되고, 더하여 의도치 않은 중복 요청을 발생시킬 위험성이 존재할지도 모른다고 판단했습니다. 이러한 문제점을 해결하고자 API 요청을 상태 변화에 따라 자동으로 발생하도록 개선했습니다.
다양한 API 요청 자동화의 트리거가 되는 값은 요청의 응답 데이터를 저장한 상태 값들이고, 해당 값들은 항상 일관된 상태를 갖기를 원했습니다. 이러한 개선 사항을 실현하기 위해 전역 상태 관리를 사용했습니다.

Zustand 전역 상태 관리 라이브러리를 도입한 후 개선된 사항은 다음과 같았습니다.

  1. 응답 데이터로 받은 값을 전역 상태로 관리
    이를 통하여 요청 자동화의 트리거가 되는 상태 값의 통일성과 신뢰성을 확보했습니다.
  2. 전역 상태를 트리거로 한 API 요청의 자동화
    다양한 시점에 필요한 API 요청의 자동화를 통하여, 사용자에게 별도의 동작을 요구하지 않고, 필요한 데이터를 반환받을 수 있게 되었습니다.

2. 전역 상태관리를 통한 에러처리 : 자동화 및 사용자 상호작용

전역 상태 관리를 사용한 이점에는 에러처리 방식도 포함이 됩니다. 해당 프로젝트에서 발생하는 대부분의 오류는 서버에서 올바른 크롤링 데이터를 반환받지 못할 때 발생합니다. 초기에는 서버단에서 발생하는 에러에 대하여 별다른 에러처리 방식이 없었습니다. 이러한 에러에 대하여 사용자에게 더욱더 나은 경험을 제공하기 위한 방법을 고민하였고, 다음과 같은 방식으로 에러처리 로직을 구현했습니다.


2-1. 에러처리 방식 : 재요청 자동화

사용자는 동영상을 시청 중이거나, 원하는 동영상을 검색하는 등 이미 프로젝트의 화면을 사용하고 있습니다. 이러한 상황에서 매번 에러가 발생할 때마다 명시적인 알람을 표시하는 처리 방식은 프로젝트와 적합하지 않다고 판단했습니다.
이러한 프로젝트의 특수성에 알맞게 최대 2회의 에러에 대하여 자동으로 재요청 로직을 구성했습니다. 2회라는 기준은 일시적인 네트워크 장애 및 서버 응답 지연에 따라 발생할 수 있는 에러를 대비한 숫자입니다. 이러한 에러에 대하여 재요청을 통해 올바른 데이터를 확보할 수 있도록 구현했습니다.


2-2. 에러처리 방식 : 선택권 부여

2회의 자동 재요청 이후에도 발생하는 에러에 대하여 사용자에게 선택권을 제공했습니다. 발생하는 에러에 대하여 그 회수를 상태 값으로 카운트합니다. 3회 이상 발생 시, 에러에 대한 문구 및 버튼을 표시하는 모달을 제공하여 사용자로 하여금 재요청을 보낼지 해당 데이터 요청을 무시할지 선택할 수 있게 권한을 부여합니다.

이러한 에러처리 방식을 통하여 프로젝트 특성에 알맞게, 너무 잦은 UI 적 표현을 최소화하면서도 사용자 상호작용을 고려한 방식을 구현했습니다.


검색에 사용되는 영어 ➡ 한글 자동 변환 기능 만들기

키워드 검색 기능에 대해 사용자 편의성을 향상할 수 있는 요소를 고민하던 중 구현된 기능입니다. 키워드를 입력하기 위해 타이핑을 할 때, 종종 영타가 입력되는 경우가 있습니다. 이럴 때, 입력된 문자를 모두 다 지우고 한글을 다시 입력하는 게 아니라, 한글로 변환 시켜주면 어떨까? 라는 생각이 들었고 해당 기능을 실행하기 위한 함수를 만들기 시작했습니다.


1. 변환을 위한 한글 파고 들기 : 초성, 중성, 종성

올바른 변환을 위해 가장 먼저 시작한 작업은 한글을 뜯어보는 것이었습니다. 한 문장을 이루는 구성 요소를 작은 단위로 따라가다보면 한 단어가 나옵니다. 그 다음은 한 글자, 마지막은 자음과 모음 등 초성입니다. 이 한 글자를 이루는 초성의 관계를 찾아보면 다음 그림과 같습니다.


이런식으로 한 글자를 이루는 초성들을 구분한 다음 3개의 요소를 배열을 통하여 상수화 했습니다. 그 다음 영문 키보드 입력과 한글의 자음, 모음을 1 대 1로 맵핑하는 객체 또한 정의했습니다. 이를 통하여 영어 ➡ 한글 변환 기능 구현을 위한 기초적인 준비를 완료했습니다.


2. 패턴을 활용한 변환 알고리즘 구현

한글을 초성, 중성, 종성의 최소 단위로 분리한 이후의 단계는 해당 요소들을 조합하고, 그 조합의 패턴을 찾아내는 일이었습니다. 위에서 분리한 초성, 중성, 종성은 바로 이 패턴의 적용을 쉽게 하기 위함이었습니다.

패턴의 구조

  1. 중성으로 끝나는 한글 (초성, 중성 2개의 요소를 사용)
    ex ) 바, 라, 코

  2. 종성으로 끝나는 한글 (초성, 중성, 종성 3개의 요소를 사용)
    ex ) 닐, 딩

위와 같이 한 글자의 끝은 받침이 없는 경우 중성으로 끝나거나, 받침이 존재할 경우 종성으로 끝나는 두 가지 패턴을 갖습니다. 이 패턴은 알고리즘을 구현하는 힌트임과 동시에 문제로 다가왔습니다.


2-1. 패턴의 구체화를 위한 임시 저장소 사용

한 글자를 구성하는 일정한 패턴은 파악했으나, 이 패턴을 코드로 구체화하는 작업은 생각과는 달랐습니다. 초기 접근 방법은 아주 단순했습니다. 입력받은 글자의 종류 (초성, 중성, 종성)을 파악하고 중성을 기준으로 지정하여 단어를 조합하는 방식이었습니다. 문제는 해당 방식으로 단어를 조합할 경우, 1번의 패턴에 해당하는 바, 라 등의 단어는 조합이 가능하였지만, 2번의 패턴인 닐, 딩 등은 조합이 불가능했습니다. 이러한 문제점을 해결하기 위해 버퍼의 동작에서 힌트를 얻은 로직을 구성했습니다.

버퍼란?
프로그래밍적 개념에서 CPU와 보조 기억 저장장치 사이 임시 저장소 역할

임시 저장소의 역할을 하는 배열을 만든 후 해당 배열에 자음, 모음을 저장했습니다. 그 후 중성과 중성 사이 글자의 초성, 종성의 개수에 따라 저장소에 저장된 자음, 모음을 조합하여 한 글자를 완성 시켰습니다. 패턴 1번의 경우 중성과 중성 사이 초성의 개수는 한 개입니다. 패턴 2번의 경우는 두 개입니다. 이러한 규칙과 임시 저장소를 활용하여, 원하는 글자를 조합할 수 있게 되었습니다.

알고리즘의 복잡도
시간 복잡도 : O(n)
공간 복잡도 : O(n)


프로그레스바를 활용한 직관적 애니메이션 구현

초기 개발 단계에서는 동영상과 관련된 애니메이션이 없이 단순히 동영상만을 출력시켜 주는 UI 였습니다. 초기 목업 데이터를 통한 동영상 재생 시에는 불편함을 느끼지 못했지만, 실제 데이터를 통해 여러 카페의 여러 동영상을 다루다 보니 큰 불편함이 존재했습니다.
카페별로 반환되는 동영상의 개수는 모두 다릅니다. 또, 영상을 시청하는 도중 새로운 동영상 데이터가 실시간으로 재생목록에 추가됩니다. 이때, "동영상이 총 몇 개인지?" , "현재 진 중인 동영상의 길이는 얼마나 긴지?" 와 관련된 정보를 직관적으로 보여준다면 사용하기 훨씬 편리해질 것 같다는 생각에 애니메이션을 구현하기 시작했습니다.


1. 초기 UI 구성 : 동영상의 총 개수 활용

프로그레스바에 총 동영상의 개수를 바탕으로 한 개별 동영상 개수를 표기했습니다. 배열을 활용하여 100%의 비율을 총 동영상의 개수로 나누었습니다. 이 값을 바탕으로 각 카페의 동영상 개수별로 또 추가되는 동영상의 개수를 반영한 UI 를 구성할 수 있었습니다.

배열 내 저장되어 있는 동영상의 위치를 이동할 경우 인덱스를 활용하여 이전의 동영상은 100% 진행률을 채운 상태로, 현재 및 이후의 동영상에 대해서는 0%의 진행률을 유지한 상태를 구현했습니다.


2. 진행률 표현 : 차오르는 애니메이션 구현

현재 동영상의 진행률을 표현할 때, HTML5의 video 태그를 사용하여 프로그레스바가 차오르는 듯한 애니메이션을 다음과 같이 구현했습니다.

2-1. useRef 와 video 태그를 활용한 재생 길이의 퍼센트 변환

진행률을 실시간으로 표기하기 위해서는 영상의 재생 길이를 알아내야 했습니다. 동영상과 관련된 정보는 HTMLMediaElement의 속성을 사용하여 얻을 수 있습니다. 현재 영상 시간을 제공해 주는 currentTime 과 영상의 총길이를 제공해 주는 duration 속성을 활용했습니다. 해당 속성값들을 토대로 길이를 퍼센트로 변환하는 함수를 작성했습니다. (currentTime / duration) * 100 이와 같은 간단한 수식을 통해서 현재 영상의 진행률을 얻어낼 수 있었습니다.


2-2. timeupdate 이벤트를 활용한 애니메이션화

동영상의 재생 상태 즉, 진행률이 변화할 때마다 프로그레스바를 자동으로 업데이트하기 위해 timeupdate 이벤트를 활용했습니다. 해당 이벤트는 동영상이 재생될 때마다 호출됩니다. 이를 통해 위에서 구한 진행률을 실시간으로 갱신시킬 수 있었습니다.
이렇게 실시간으로 업데이트되는 진행률을 사용하여 차오르는 애니메이션을 구현할 수 있었습니다.

애니메이션화 UI

프로젝트 후기

팀 프로젝트가 끝난 이후 진행한 개인 프로젝트라 팀의 소중함이 더 크게 다가왔던 시간이었습니다. 팀으로 진행할 때는 내 생각에 대한 피드백이 즉각적으로 돌아왔고, 그로 인해 의사결정을 빠르게 바른 방향으로 내릴 수 있었습니다. 하지만, 이번 프로젝트는 처음 기획부터 마무리까지 모든 단계를 오롯이 나의 생각으로만 방향을 잡아가야 했기에 어려움이 컸었습니다. 이러한 시간속에, 내가 정한 방향이 항상 옳은 방향은 아니지만 최소한 더 긍정적이며 바른 방향으로 나아가는 방법을 배웠다고 생각합니다. 앞으로 실무에서도 항상 더 바른 방향성에 대해 고민을 하는 개발자가 되고자 합니다.

타입스크립트로 변환을 마치며 ( ~ 25.04.15)

제한된 기간 안에 프로젝트를 완성해야 했기에, 기존에 익숙했던 자바스크립트를 기반으로 코드를 작성했습니다. 개인적으로, 자바스크립트에 비해 더 높은 안정성을 갖는 언어로 평가되는 타입스크립트에 대한 호기심이 있었기 때문에 프로젝트 초안을 완성한 이후 타입스크립트로의 변환을 진행해 보았습니다.

사실 변환을 마무리하고 후기를 작성하는 지금 시점에서도 기능적인 측면에서 타입스크립트가 자바스크립트에 비해 어떤 점에서 더 우수한지는 체감하지 못했습니다. 이 부분에 대해서는 두 언어 간의 차이점을 보다 명확히 확인해볼 수 있도록, 몇 가지 테스트 진행을 통하여 보완해 보려 합니다. 하지만 협업의 관점에서 접근해 보자면, 기업이 타입스크립트를 선호하는 이유에 대해 어느정도 체감해 볼 수 있었습니다.

제가 느낀 가장 큰 이유는 코드의 가독성 향상과 내용 유추 입니다. 처음 보는 코드에서 전후 맥락 없이 내용을 파악하는 일은 꽤 어렵습니다. 하지만 타입스크립트로 작성된 코드는 사용하는 변수, 함수들에 대하여 명시적으로 타입이 지정되어 있습니다. 이는 타입을 통해서 보다 쉽게 코드의 내용을 유추하는데 더 많은 도움을 줄 수 있다 느껴지는 부분이었습니다. 유사한 맥락으로, type 혹은 interface 키워드를 통하여 여러 변수의 타입을 한 곳에서 지정하기에, 추후 코드를 유지보수 하는 과정에서도 더 편리함을 느낄 수 있었습니다.

이번 변환 과정을 통해 단순히 문법을 변경한다기보다, 협업을 위한 코드 설계를 한 번 더 생각해 볼 수 있는 계기가 되었습니다.

About

사용자 가입 네이버 카페 기반 동영상을 모아 보여주는 숏츠 플랫폼

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published