네이버 API 기반에서 크롤링 기반 자체 DB로 구조 전환 설계

송현진·2025년 7월 24일
0

프로젝트 회고

목록 보기
14/17

문제 인식

현재 무무의 선물상자 서비스는 네이버 쇼핑 API를 기반으로 실시간 상품을 검색해 추천하고 있다. 하지만 실제 운영 과정에서 다음과 같은 한계에 부딪혔다.

  • AAPI 응답 품질이 일정하지 않고, 태그나 키워드 조합에 따라 엉뚱한 결과가 나오기도 함
  • 카테고리, 리뷰 수, 가격대 등의 조건을 정교하게 반영하기 어려움
  • 외부 API에 의존하다 보니 추천 품질을 서버 측에서 통제하기 어려움

이러한 이유로 서비스 품질을 우리가 주도적으로 제어할 수 있는 구조, 즉 크롤링 기반 자체 상품 DB 구축으로 방향을 전환하게 되었다.

구조 설계 목표

  • 상품 데이터를 직접 크롤링하여 DB에 저장
  • 수집된 데이터는 검수 및 컨펌 절차를 거쳐 추천 대상에 포함
  • 단건 URL 등록 / 키워드 기반 수집 / 카테고리 기반 수집 등 다양한 방식 지원
  • 관리자 페이지에서 상품을 직접 확인하고 컨펌, 수정, 거절, 우선순위 지정까지 가능한 구조 제공

크롤링 전략

크롤링 대상은 네이버 기프트샵, 네이버 스마트스토어, 쿠팡 등 다양한 사이트다. 사이트마다 HTML 구조가 다르기 때문에 도메인 또는 URL 패턴을 기반으로 레이아웃을 식별하고 각 레이아웃에 맞는 파서 클래스를 분리해 처리하도록 설계했다.

if (url.contains("shopping.naver.com/gift")) return NAVER_GIFT_V1;
else if (url.contains("coupang.com")) return COUPANG_V1;
  • 파싱 로직은 레이아웃마다 전담 클래스로 분리하여 유지보수성을 높임
  • 키워드 검색, 카테고리 검색, URL 직접 등록 등 다양한 방식으로 수집 가능
  • 수집된 상품은 기본적으로 isConfirmed = false 상태로 저장되어 검수 대상이 됨

허용되지 않은 도메인 처리

  • 상품 URL 등록 시 내부 화이트리스트를 기준으로 도메인을 검사
  • 예를 들어 randomshop.com 같은 지원하지 않는 사이트일 경우 크롤링을 차단
  • "지원하지 않는 사이트입니다" 와 같은 사용자 친화적인 에러 메시지 반환

DB 구조

크롤링한 상품과 컨펌된 추천용 상품을 별도 테이블로 나누는 방식도 고려했지만 결국 하나의 Product 테이블에서 isConfirmed 플래그로 구분하는 것이 더 직관적이고 관리가 편하다는 판단을 내렸다.

@Entity
public class Product {

    @Id
    private Long id;

    private String title;             // 원 제목
    private String displayName;       // UI 노출용 이름 (optional)
    private int price;

    private String brand;             // 브랜드명 (optional)
    private String imageUrl;
    private String link;              // 상품 상세 페이지 URL

    @Enumerated(EnumType.STRING)
    private StoreType storeType;      // GIFT_NAVER, COUPANG 등

    @Enumerated(EnumType.STRING)
    private Category category;

    private boolean isConfirmed;      // 검수 완료 여부
    private int priorityScore;        // 리뷰 수 등 기준으로 산정

    private LocalDateTime createdAt;
}

관리자 기능 흐름

1. 수동 등록 (단건)

  • 관리자가 상품 URL을 입력하면
    → 도메인 유효성 검사
    → 파싱 성공 시 미리보기 제공
    → “등록” 버튼 클릭 시 Product 테이블에 저장 (isConfirmed = true)

수동 등록의 경우 신뢰 가능한 출처에서 선택적으로 추가되므로 곧바로 컨펌된 상태로 저장

2. 자동 수집

  • 키워드 기반 검색, 카테고리 페이지 URL 기반 수집 등 다양한 방식으로 상품을 자동 크롤링
  • 자동 수집된 상품은 isConfirmed = false 상태로 저장되어 검수가 필요함
POST /api/admin/crawler/search?keyword=여자 향수
POST /api/admin/crawler/url?link=https://giftshop.naver.com/category/100

3. 검수/컨펌

  • 관리자 페이지에서 상품 목록을 최신순 확인 가능
  • 필터: isConfirmed, 카테고리, 가격대 등
  • 선택 항목을 컨펌하거나 Reject 처리 가능
    • 전체 선택 후 일괄 승인/거절
    • 수정 후 컨펌도 가능
PUT /api/admin/products/confirm
Body: {
  id: [1, 2, 3],
  isConfirmed: true
}

4. 수정/삭제

  • 단건 또는 리스트로 상품 수정 가능
    • 리스트 수정은 덮어쓰기 방식도 고려
  • 다건 삭제도 가능

📝 느낀점

이번 구조 설계를 진행하며 느낀 가장 큰 변화는 추천 품질에 대한 주도권이 API 공급자가 아닌 우리에게 넘어온다는 점이었다. 기존 네이버 API 기반 시스템은 빠르게 결과를 얻을 수 있다는 장점은 있었지만 필터링이 제한적이고 결과 품질이 불안정해 사용자 경험을 예측하기 어려웠다. 반면 크롤링을 통해 우리가 원하는 상품을 직접 수집하고 내부적으로 검수 및 우선순위를 지정할 수 있는 구조를 갖추게 되면 추천의 품질과 방향성을 훨씬 정교하게 통제할 수 있게 된다.

또한 ProductCrawledProduct를 분리하지 않고 isConfirmed로 구분하는 구조는 단순화 측면에서 현명한 선택이었다. 도메인 설계가 복잡해질 수 있는 상황에서 관리 편의성과 코드 유지보수성 모두를 고려한 실용적인 결정이었다고 생각한다.

profile
개발자가 되고 싶은 취준생

0개의 댓글