[Spring] - Naver Api로 상품 검색 목록 받아오기

💨 요청 흐름

  1. 사용자가 게시글 작성
  2. 해당 게시글의 제목과 관련된 상품 목록 받아오기

링크에 들어가 사진과 같이 작성한다.

스크린샷

현재 포스팅에서 진행할 내용은 검색 API이며, 이 외에 필요한 API는 추가로 넣어도 무관하다.

Spring Boot 적용

모든 자세한 설명은 공식 문서에 잘 나와있습니다!
공식 문서를 한 번 읽어보신 후 제 글을 봐주시면 감사하겠습니다! 😃

application.yml

Developers에서 어플리케이션을 등록하면, Client ID, Client Secret을 발급해준다. 이를 application.yml 설정 파일에 복사해서 넣어주면 된다.

단, 테스트라도 Secret 키는 외부에 공개되면 좋지 않기 때문에 설정 파일은 가능한 .gitignore에 등록해준다.

naver:
  client-id : ...
  client-secret : ...

만약 application.properties를 사용 중이라면, 아래와 같이 만들면 된다.

naver.client-id : ...
naver.client-secret : ...

Template 생성

@Component
public class NaverSearchApi {

    @Value("${naver.client-id}")
    private String CLIENT_ID;

    @Value("${naver.client-secret}")
    private String CLIENT_SECRET;

    private static final String REQUEST_URL = "https://openapi.naver.com/v1/search/shop.json?";
    private static final String PARAMETER_INFO = "query=%s&display=%d&start=1&sort=%s";
}

@Component를 통해 빈 등록을 해주고, @Value()를 통해 설정 파일에 있는 값을 가져온다. REQUEST_URL은 변경될 일이 없고, 호출 빈도가 잦기 때문에 static final 키워드를 붙여 정적 변수로 저장해둔다.

@Component
@Slf4j
public class NaverSearchApi {
    
    // 기존 코드 생략
    ...

    // 요청을 보낼 때 함께 보낼 파라미터
    // 자세한 내용은 공식 문서 확인
    private final int DISPLAY = 5;
    private final String SORT = "sim";

    /**
     * @apiNote 네이버에 요청을 보내는 메소드
     * @param keyword 네이버 쇼핑에 검색할 키워드
     * */
    public List<Map<String, String>> getResult(String keyword) {
        // 키워드를 UTF-8 인코딩
        String encodedKeyword = keyword;
        try {
            encodedKeyword = URLEncoder.encode(keyword, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }

        // 요청 URL에 파라미터와 그에 맞는 값 매핑
        String requestUrl = REQUEST_URL + PARAMETER_INFO.formatted(encodedKeyword, DISPLAY, SORT);

        // 요청을 보내기 위한 RestTemplate
        RestTemplate restTemplate = new RestTemplate();
        
        // 공식 문서에 나와 있듯이, 요청 헤더에 클라이언트 아이디 및 시크릿 추가
        HttpHeaders headers = new HttpHeaders();
        headers.set("X-Naver-Client-Id", CLIENT_ID);
        headers.set("X-Naver-Client-Secret", CLIENT_SECRET);

        // RestTemplate을 이용해 필요한 정보를 담아 요청을 보내 값을 String 타입으로 반환(exchange) 받음
        ResponseEntity<String> responseEntity = restTemplate.exchange(requestUrl, HttpMethod.GET, new HttpEntity<>(headers), String.class);
        
        // RestTemplate을 통해 반환 받은 Map을 JSON 형식으로 변환 후 가공한 다음 Controller에 반환
        return getJsonObject(responseEntity.getBody());
    }
}

위와 같이 요청을 보내면 아래와 같은 결과가 나오게 된다.

{
    "lastBuildDate": "Wed, 23 Aug 2023 19:49:07 +0900",
    "total": 8448,
    "start": 1,
    "display": 2,
    "items": [
        {
            "title": "<b>2021 맥북 프로 M2</b> M1 Pro Max 16인치 A2780",
            "link": "...",
            "image": "...",
            "lprice": "21210",
            "hprice": "",
            "mallName": "...",
            "productId": "...",
            "productType": "2",
            "brand": "",
            "maker": "",
            "category1": "디지털/가전",
            "category2": "노트북",
            "category3": "",
            "category4": ""
        },
        {
            "title": "<b>2021 맥북 프로 M2</b> M1 Pro Max 14인치 A",
            "link": "...",
            "image": "...",
            "lprice": "22380",
            "hprice": "",
            "mallName": "...",
            "productId": "...",
            "productType": "2",
            "brand": "",
            "maker": "",
            "category1": "디지털/가전",
            "category2": "노트북",
            "category3": "",
            "category4": ""
        }
    ]
}

이제 우리에게 필요한 items에 있는 내용만 빼면 된다. 이후 코드에서 List<Map<String, String>> 형태가 나오는데 해당 코드는 아래와 같이 이해하면 된다.

하나의 검색 결과가 Key : Value 형태로 합쳐져 하나의 Map을 이룸

{
    "title": "<b>2021 맥북 프로 M2</b> M1 Pro Max 16인치 A2780",
    "link": "...",
    "image": "...",
    "lprice": "21210",
    "hprice": "",
    "mallName": "...",
    "productId": "...",
    "productType": "2",
    "brand": "",
    "maker": "",
    "category1": "디지털/가전",
    "category2": "노트북",
    "category3": "",
    "category4": ""
}

즉, List 자체에는 검색 결과의 개수가 담기는 것이고, Map 자체에는 한 검색 결과에 대한 상세 값들이 담기는 것이다.

아래 코드에서 사용할 JSON은 json-simple 라이브러리이다.

implementation 'com.googlecode.json-simple:json-simple:1.1.1'

그럼 위 검색 결과에서 필요한 정보만 가져와 가공하는 코드를 작성해보자!

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

public class NaverSearchApi {
    
    // 기존 코드 생략
    ...

    /**
     * @apiNote RestTemplate을 통해 받아온 데이터를 가공하는 메소드
     * @param resultData RestTemplate을 통해 받아온 JSON 형태의 문자열 데이터
     * */
    private List<Map<String, String>> getJsonObject(String resultData) {
        // Key : Value 짝으로 된 하나의 검색 결과
        List<Map<String, String>> extractedItems = new ArrayList<>();
        
        // 앞서 RestTemplate을 통해 반환 받은 문자열을 JSONObject(Json 객체)로 변환
        JSONObject json = (JSONObject) JSONValue.parse(resultData);
        
        // 변환한 JSON 객체에서 키 값이 item인 부분만 가져옴
        JSONArray documents = (JSONArray) json.get("items");
        
        // item에 담긴 검색 결과를 탐색하며, 필요한 데이터만 가공
        for (Object item : documents) {
            
            // 필요한 데이터만 저장하기 위해 Map 생성 
            Map<String, String> map = new LinkedHashMap<>();

            // JSONArray에서 꺼내온 객체가 Object 타입이기 때문에 JSONObject로 변환
            JSONObject obj = (JSONObject) item;
            
            // 필요한 값들만 가져와 map에 추가
            map.put("title", (String) obj.get("title"));
            map.put("link", (String) obj.get("link"));
            map.put("brand", (String) obj.get("brand"));
            map.put("lprice", (String) obj.get("lprice"));
            
            // 완성된 map을 최종 결과물 List에 추가
            extractedItems.add(map);
        }
        return extractedItems;
    }
}

이렇게 구성하면, 내가 원하는 데이터만 가져올 수 있게된다.

{
    "title": "Apple <b>맥북</b> <b>프로</b> 14 2023년 <b>M2 Pro</b> 10코어 실버 (MPHH3KH/A)",
    "link": "...",
    "brand": "Apple",
    "lprice": "2600000"
},
{
    "title": "MNW93KH/A Apple <b>맥북</b><b>프로</b><b>m2</b> <b>맥북</b>16인치 2023 <b>M2 Pro</b> 1tb 그레이",
    "link": "...",
    "brand": "Apple",
    "lprice": "3189000"
},
{
    "title": "Apple <b>맥북</b> <b>프로</b> 16 2023년 <b>M2 Pro</b> 12코어 스페이스 그레이 (MNW93KH/A)",
    "link": "...",
    "brand": "Apple",
    "lprice": "3250000"
},
{
    "title": "Apple <b>맥북</b> <b>프로</b> 14 2023년 <b>M2 Pro</b> 10코어 스페이스 그레이 (MPHE3KH/A)",
    "link": "...",
    "brand": "Apple",
    "lprice": "2542000"
},
{
    "title": "Apple 2022 <b>맥북</b> <b>프로</b> 13 <b>M2</b>  스페이스 그레이  GPU 10코어  256GB  24GB  Z16R0001M",
    "link": "...",
    "brand": "Apple",
    "lprice": "2200000"
}

전체 코드는 Github를 참고해주세요!

🤔 회고

조금 더 효율적으로 데이터를 가져오고, 가공할 수 있도록 공부해야할 것 같다!

댓글남기기