현재 무무의 선물상자 서비스는 네이버 쇼핑 API
를 기반으로 실시간 상품을 검색해 추천하고 있다. 하지만 실제 운영 과정에서 다음과 같은 한계에 부딪혔다.
이러한 이유로 서비스 품질을 우리가 주도적으로 제어할 수 있는 구조, 즉 크롤링 기반 자체 상품 DB 구축으로 방향을 전환하게 되었다.
크롤링 대상은 네이버 기프트샵
, 네이버 스마트스토어
, 쿠팡
등 다양한 사이트다. 사이트마다 HTML 구조가 다르기 때문에 도메인 또는 URL 패턴을 기반으로 레이아웃을 식별하고 각 레이아웃에 맞는 파서 클래스를 분리해 처리하도록 설계했다.
if (url.contains("shopping.naver.com/gift")) return NAVER_GIFT_V1;
else if (url.contains("coupang.com")) return COUPANG_V1;
isConfirmed = false
상태로 저장되어 검수 대상이 됨randomshop.com
같은 지원하지 않는 사이트일 경우 크롤링을 차단"지원하지 않는 사이트입니다"
와 같은 사용자 친화적인 에러 메시지 반환크롤링한 상품과 컨펌된 추천용 상품을 별도 테이블로 나누는 방식도 고려했지만 결국 하나의 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;
}
Product
테이블에 저장 (isConfirmed = true
)수동 등록의 경우 신뢰 가능한 출처에서 선택적으로 추가되므로 곧바로 컨펌된 상태로 저장
isConfirmed = false
상태로 저장되어 검수가 필요함POST /api/admin/crawler/search?keyword=여자 향수
POST /api/admin/crawler/url?link=https://giftshop.naver.com/category/100
isConfirmed
, 카테고리, 가격대 등PUT /api/admin/products/confirm
Body: {
id: [1, 2, 3],
isConfirmed: true
}
이번 구조 설계를 진행하며 느낀 가장 큰 변화는 추천 품질에 대한 주도권이 API 공급자가 아닌 우리에게 넘어온다는 점이었다. 기존 네이버 API 기반 시스템은 빠르게 결과를 얻을 수 있다는 장점은 있었지만 필터링이 제한적이고 결과 품질이 불안정해 사용자 경험을 예측하기 어려웠다. 반면 크롤링을 통해 우리가 원하는 상품을 직접 수집하고 내부적으로 검수 및 우선순위를 지정할 수 있는 구조를 갖추게 되면 추천의 품질과 방향성을 훨씬 정교하게 통제할 수 있게 된다.
또한 Product
와 CrawledProduct
를 분리하지 않고 isConfirmed
로 구분하는 구조는 단순화 측면에서 현명한 선택이었다. 도메인 설계가 복잡해질 수 있는 상황에서 관리 편의성과 코드 유지보수성 모두를 고려한 실용적인 결정이었다고 생각한다.