[Spring] - Naver Api๋ก ์ํ ๊ฒ์ ๋ชฉ๋ก ๋ฐ์์ค๊ธฐ
๐จ ์์ฒญ ํ๋ฆ
- ์ฌ์ฉ์๊ฐ ๊ฒ์๊ธ ์์ฑ
- ํด๋น ๊ฒ์๊ธ์ ์ ๋ชฉ๊ณผ ๊ด๋ จ๋ ์ํ ๋ชฉ๋ก ๋ฐ์์ค๊ธฐ
Naver Developers ๋ฑ๋ก
๋งํฌ์ ๋ค์ด๊ฐ ์ฌ์ง๊ณผ ๊ฐ์ด ์์ฑํ๋ค.
ํ์ฌ ํฌ์คํ
์์ ์งํํ ๋ด์ฉ์ ๊ฒ์
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๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์!
๐ค ํ๊ณ
์กฐ๊ธ ๋ ํจ์จ์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ , ๊ฐ๊ณตํ ์ ์๋๋ก ๊ณต๋ถํด์ผํ ๊ฒ ๊ฐ๋ค!