쿠팡파트너스 OpenAPI를 활용해 상품 검색 및 추천 기능을 구현하던 중 쿼터 제한으로 인해 실서비스에 적용하기 어렵다는 판단을 내렸다.
팀 프로젝트에서 실시간 추천 상품을 확보해야 하는 구조였기 때문에 API 호출 수 제한은 매우 치명적인 병목 요인이 되었다. 그렇기 때문에 다른 방법들을 알아보다 네이버 API에 대해서 알게 되었다. 네이버 API는 아래와 같은 이점이 있다.
가장 중요했던 쿼터 문제를 해결할 수 있었고 네이버 검색 알고리즘 기반으로 작동하기 때문에 추천 품질에도 긍정적인 영향을 줄 수 있다고 생각했다. 또한 오픈마켓 형식으로 굉장히 많은 상품들이 있다. 하지만 가격 필터링 기능은 제공하지 않고 초당 최대 10회 호출 제한이 존재하는 점에 유의해야 한다. 그럼에도 굉장히 좋은 해결책이 되기 때문에 구조를 변경하기로 했다.
네이버 검색 쇼핑 API 문서 - 응답 값
<rss version="2.0">
<channel>
<title>Naver Open API - shop ::'가방'</title>
<link>http://search.naver.com</link>
<description>Naver Search Result</description>
<lastBuildDate>Tue, 04 Oct 2016 13:23:58 +0900</lastBuildDate>
<total>17161390</total>
<start>1</start>
<display>10</display>
<item>
<title>허니트립 보스턴백</title>
<link>http://openapi.naver.com/l?AAABWLsQ7CIBRFv+Z1JLzSShkYqLajRmPcG6TQRCgiNunfizdnODnJfX9N2iUMCnoKHYWh/4sSlUtmli7nCExBPRY+bo1xCZaEaTOJ6NWXaKdsSHAB2Lg8gZ2QMmybA0cuqiyx4W0ZZR2KrvJyx2CPV3RQ95fbnDT3r+Fh2kbfz5su7x8wIs7ZjgAAAA==</link>
<image>http://shopping.phinf.naver.net/main_1031546/10315467179.jpg</image>
<lprice>6700</lprice>
<hprice>0</hprice>
<mallName>허니트립</mallName>
<productId>10315467179</productId>
<productType>2</productType>
<brand></brand>
<maker>허니트립</maker>
<category1>패션잡화</category1>
<category2>여행용가방/소품</category2>
<category3>보스턴백</category3>
<category4></category4>
</item>
...
</channel>
</rss>
@Slf4j
@Component
@RequiredArgsConstructor
public class NaverApiClient {
private final RestTemplate restTemplate = new RestTemplate();
@Value("${naver.client-id}")
private String clientId;
@Value("${naver.client-secret}")
private String clientSecret;
public List<ProductResponseDto> search(String query, int page, int display) {
int start = (page - 1) * display + 1;
String encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8);
String url = "https://openapi.naver.com/v1/search/shop.json?query=" + encodedQuery +
"&display=" + display + "&start=" + start;
log.info("네이버 쇼핑 API 요청 시작: query='{}'", query);
log.debug("요청 URL: {}", url);
HttpHeaders headers = new HttpHeaders();
headers.set("X-Naver-Client-Id", clientId);
headers.set("X-Naver-Client-Secret", clientSecret);
HttpEntity<?> entity = new HttpEntity<>(headers);
ResponseEntity<JsonNode> response = restTemplate.exchange(
URI.create(url), HttpMethod.GET, entity, JsonNode.class
);
log.info("네이버 응답 상태 코드: {}", response.getStatusCode());
JsonNode body = response.getBody();
log.debug("네이버 응답 바디: {}", body);
JsonNode items = body.get("items");
if (items == null || !items.isArray()) {
log.warn("items 노드가 없거나 배열이 아님. 전체 응답: {}", body);
return List.of();
}
List<ProductResponseDto> result = new ArrayList<>();
for (JsonNode item : items) {
result.add(new ProductResponseDto(
null,
item.get("title").asText(),
item.get("link").asText(),
item.get("image").asText(),
item.get("lprice").asInt(),
item.get("mallName").asText()
));
}
log.info("파싱된 상품 수: {}", result.size());
return result;
}
}
X-Naver-Client-Id
, X-Naver-Client-Secret
을 이용한 헤더 기반 인증/v1/search/shop.json
엔드포인트에 query
, display
, start
파라미터를 조합하여 요청display
: 한 번의 요청에서 가져올 상품의 수 (기본값 : 10, 최대값 : 100)start
: 검색 시작 위치 (1부터 시작, start + display - 1
이 최대 1000을 초과할 수 없음)
display
와start
두 개의 파라미터를 조합해 페이지네이션 처리를 구현할 수 있다.
예를 들어 3페이지를 요청할 경우start = (3 - 1) * display + 1 = 201
로 설정하면 된다.
네이버 API는start + display -1 <= 1000
이라는 조건이 있기 때문에 최대 1000개까지만 조회 가능하다는 점을 고려해야 한다.
쿠팡파트너스 API로 구현을 진행하던 중 호출 제한 이슈로 인해 예상치 못한 문제가 발생했고 일정이 촉박한 상황이라 당황스러웠다. 하지만 빠르게 해결책을 마련해야 했기 때문에 멘토님과 팀원분들께 공유하고 다양한 대안을 찾아보기 시작했다. 다행히 네이버 검색 쇼핑 API라는 것을 찾게 되었고 승인 조건 없이 바로 테스트가 가능하다는 점에서 빠르게 적용해보았다. 이를 테스트 해보고 쿠팡파트너스 API보다 네이버 API가 호출 안정성과 응답 품질이 더 좋다고 판단해 구조 전환을 결정했고 현재는 DB 구조를 변경과 추천 로직 리팩토링을 진행 중이다.
이번 API 전환은 단순한 연동 이상의 의미가 있었다. 외부 API를 도입할 때 가장 우선적으로 검토해야 할 것이 호출 제한, 승인 조건, 기능 제공 범위라는 점을 체감했다.
쿠팡 API는 제한이 많고 유동적이라 실제 서비스에서는 리스크가 컸다. 반면 네이버 쇼핑 API는 제약이 명확하고 문서화가 잘 되어 있었으며 실시간 추천 시스템에 더 적합한 구조였다. 이번 전환으로 인해 추천 품질과 응답 속도를 모두 개선할 수 있었고 앞으로도 외부 변화에 유연하게 대응할 수 있는 구조적 사고와 대응 역량이 중요하다는 것을 배웠다.