본문 바로가기
웹개발/SpringBoot

네이버 검색 API를 이용하기

by 철없는민물장어 2023. 7. 26.
728x90
반응형

외부 API를 이용해보는 경험을 쌓고자 백과사전 검색기능을 프로젝트에 추가해보기로 했다.

 

네이버 검색(백과사전) API를 이용해서, 검색어를 입력하면 검색결과를 보여주도록 만들어보자.


사전 준비

 

https://developers.naver.com/docs/serviceapi/search/blog/blog.md#%EB%B8%94%EB%A1%9C%EA%B7%B8

 

검색 > 블로그 - Search API

검색 > 블로그 블로그 검색 개요 개요 검색 API와 블로그 검색 개요 검색 API는 네이버 검색 결과를 뉴스, 백과사전, 블로그, 쇼핑, 영화, 웹 문서, 전문정보, 지식iN, 책, 카페글 등 분야별로 볼 수

developers.naver.com

위 링크로 접속해서, API 이용방법을 숙지.

(로그인 후 이용신청을 해야 id와 시크릿키를 발급받을 수 있다)

 


파라미터는... 일단은 검색어 query만 신경쓰면 될 것 같다. (display와 start는 페이징을 위해 필요한것 같은데, 지금은 생략함)

 

< HTTP/1.1 200 OK
< Server: nginx
< Date: Mon, 26 Sep 2016 01:50:00 GMT
< Content-Type: text/xml;charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Keep-Alive: timeout=5
< Vary: Accept-Encoding
< X-Powered-By: Naver
< Cache-Control: no-cache, no-store, must-revalidate
< Pragma: no-cache
<
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>Naver Open API - encyc ::'주식'</title>
        <link>http://search.naver.com</link>
        <description>Naver Search Result</description>
        <lastBuildDate>Mon, 26 Sep 2016 10:50:00 +0900</lastBuildDate>
        <total>28034</total>
        <start>1</start>
        <display>10</display>
        <item>
            <title><b>주식</b></title>
            <link>http://openapi.naver.com/l?AAAB2NPQ+CMBiEf83LSPrxAu3QAQoYYhwZdCNQRSMFaiXh31tMbrjncpdbv8btCioNOYdCH0YUIMtodOauRu8X4DmwOsgbN31i223Gxf08hcRY7/bYjhZ4Pcx9MwAvKUUmmQCW9s+DkUhkB3XePGa3/0ucCk4iryhmKBJMCcFERpPSTXVdX1ty0XijnCzh45zRdcnb07sNux+iWggwrgAAAA==</link>
            <description> <b>주식</b>회사의 자본을 이루는 단위로서의 금액 및 이를 전제로 한 주주의 권리·의무(주주권).  <b>주식</b>회사는 자본단체이므로 자본이 없이는 성립할 수 없다. 자본은 사원인 주주(株主)의 출자이며, 권리와 의무의... </description>
            <thumbnail />
        </item>
        ...
    </channel>
</rss>

응답예시.

 

네이버에서 사용예를 제공해준다.

// 네이버 검색 API 예제 - 블로그 검색
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;


public class ApiExamSearchBlog {


    public static void main(String[] args) {
        String clientId = "YOUR_CLIENT_ID"; //애플리케이션 클라이언트 아이디
        String clientSecret = "YOUR_CLIENT_SECRET"; //애플리케이션 클라이언트 시크릿


        String text = null;
        try {
            text = URLEncoder.encode("그린팩토리", "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("검색어 인코딩 실패",e);
        }


        String apiURL = "https://openapi.naver.com/v1/search/blog?query=" + text;    // JSON 결과
        //String apiURL = "https://openapi.naver.com/v1/search/blog.xml?query="+ text; // XML 결과


        Map<String, String> requestHeaders = new HashMap<>();
        requestHeaders.put("X-Naver-Client-Id", clientId);
        requestHeaders.put("X-Naver-Client-Secret", clientSecret);
        String responseBody = get(apiURL,requestHeaders);


        System.out.println(responseBody);
    }


    private static String get(String apiUrl, Map<String, String> requestHeaders){
        HttpURLConnection con = connect(apiUrl);
        try {
            con.setRequestMethod("GET");
            for(Map.Entry<String, String> header :requestHeaders.entrySet()) {
                con.setRequestProperty(header.getKey(), header.getValue());
            }


            int responseCode = con.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) { // 정상 호출
                return readBody(con.getInputStream());
            } else { // 오류 발생
                return readBody(con.getErrorStream());
            }
        } catch (IOException e) {
            throw new RuntimeException("API 요청과 응답 실패", e);
        } finally {
            con.disconnect();
        }
    }


    private static HttpURLConnection connect(String apiUrl){
        try {
            URL url = new URL(apiUrl);
            return (HttpURLConnection)url.openConnection();
        } catch (MalformedURLException e) {
            throw new RuntimeException("API URL이 잘못되었습니다. : " + apiUrl, e);
        } catch (IOException e) {
            throw new RuntimeException("연결이 실패했습니다. : " + apiUrl, e);
        }
    }


    private static String readBody(InputStream body){
        InputStreamReader streamReader = new InputStreamReader(body);


        try (BufferedReader lineReader = new BufferedReader(streamReader)) {
            StringBuilder responseBody = new StringBuilder();


            String line;
            while ((line = lineReader.readLine()) != null) {
                responseBody.append(line);
            }


            return responseBody.toString();
        } catch (IOException e) {
            throw new RuntimeException("API 응답을 읽는 데 실패했습니다.", e);
        }
    }
}

너무 잘 나와있어서 그대로 사용해도 될 것 같다..

 

1. Service

@Service
@Transactional
public class NaverSearchAPIService {
        public static NaverSearchResDto main(String arg) {

            String clientId = "id"; //애플리케이션 클라이언트 아이디
            String clientSecret = "secret"; //애플리케이션 클라이언트 시크릿


            String text = null;
            try {
                text = URLEncoder.encode(arg, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException("검색어 인코딩 실패",e);
            }


            String apiURL = "https://openapi.naver.com/v1/search/encyc.json?query=" + text;    // JSON 결과
            //String apiURL = "https://openapi.naver.com/v1/search/blog.xml?query="+ text; // XML 결과


            Map<String, String> requestHeaders = new HashMap<>();
            requestHeaders.put("X-Naver-Client-Id", clientId);
            requestHeaders.put("X-Naver-Client-Secret", clientSecret);
            String responseBody = get(apiURL,requestHeaders);

//
            System.out.println(responseBody);
//            return responseBody;
            return NaverSearchResDto.fromJson(responseBody);
        }


        private static String get(String apiUrl, Map<String, String> requestHeaders){
            HttpURLConnection con = connect(apiUrl);
            try {
                con.setRequestMethod("GET");
                for(Map.Entry<String, String> header :requestHeaders.entrySet()) {
                    con.setRequestProperty(header.getKey(), header.getValue());
                }


                int responseCode = con.getResponseCode();
                if (responseCode == HttpURLConnection.HTTP_OK) { // 정상 호출
                    return readBody(con.getInputStream());
                } else { // 오류 발생
                    return readBody(con.getErrorStream());
                }
            } catch (IOException e) {
                throw new RuntimeException("API 요청과 응답 실패", e);
            } finally {
                con.disconnect();
            }
        }


        private static HttpURLConnection connect(String apiUrl){
            try {
                URL url = new URL(apiUrl);
                return (HttpURLConnection)url.openConnection();
            } catch (MalformedURLException e) {
                throw new RuntimeException("API URL이 잘못되었습니다. : " + apiUrl, e);
            } catch (IOException e) {
                throw new RuntimeException("연결이 실패했습니다. : " + apiUrl, e);
            }
        }


        private static String readBody(InputStream body){
            InputStreamReader streamReader = new InputStreamReader(body);


            try (BufferedReader lineReader = new BufferedReader(streamReader)) {
                StringBuilder responseBody = new StringBuilder();


                String line;
                while ((line = lineReader.readLine()) != null) {
                    responseBody.append(line);
                }


                return responseBody.toString();
            } catch (IOException e) {
                throw new RuntimeException("API 응답을 읽는 데 실패했습니다.", e);
            }
        }
    }

네이버에서 제공하는 예시코드를 그대로 갖다쓰고, 필요한 몇가지 부분만 수정했다.

우선 id와 secret값을 본인이 발급받은 값으로 수정해야한다.

(이 정보들은 설정파일에 작성해두고, @Value로 끌어다 쓰는것이 좋을 것 같다)

 

그리고, 기존의 main메소드는 JSON데이터를 String타입으로 반환하는데, 나는 객체로 반환되었으면 좋겠다는 생각이 들어서 Dto.fromJson()메소드를 별도로 작성하고 JSON을 DTO로 변환하여 리턴하였다.

(뷰로 데이터를 넘겨야하는데 String으로 넘겨도 뷰에서 잘 적용할 수 있을까?라는 의문이 들어서.. 일단 변환해보기로 함.)

@Getter
@AllArgsConstructor
@NoArgsConstructor
public class NaverSearchResDto {
    private String lastBuildDate;
    private int total;
    private int start;
    private int display;
    private List<SearchItemDto> items = new ArrayList<>();
    
    public static NaverSearchResDto fromJson(String json){
        ObjectMapper objectMapper = new ObjectMapper();

        try {
            // JSON을 Dto 객체로 매핑
            return objectMapper.registerModule(new JavaTimeModule()).readValue(json, NaverSearchResDto.class);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}
@Getter @Setter
public class SearchItemDto {
    private String title;
    private String link;
    private String description;
    private String thumbnail;
}

객체로 매핑하기 위해서 SearchItemDto, NaverSearchResDto 클래스를 작성했다.

 

2. Controller

 

@Controller
@RequiredArgsConstructor
@Slf4j
public class SearchController {

    private final SearchService searchService;

    @GetMapping("/search")
    public String search(@RequestParam String keyword, Model model){
        NaverSearchResDto naverSearchResDto = NaverSearchAPIService.main(keyword);

        model.addAttribute("res",naverSearchResDto);
        model.addAttribute("keyword",keyword);
        return "search";
    }

}

파라미터로 키워드를 받고, 서비스로 넘겨서 결과를 반환받는다.

이후 뷰로 전달.

 

3.View

 

{{>layouts/header}}

<div class="container p-5">
    <h2>"{{keyword}}"에 대한 검색 결과</h2>
    {{#res}}
        <p>검색량: {{total}}</p>
        <div class="row row-cols-1 row-cols-md-2 g-4">
            {{#items}}
                <div class="col">
                    <div class="card">
                        <div class="card-body">
                            <h5 class="card-title">{{{title}}}</h5>
                            {{#thumbnail}}
                                <img src="{{thumbnail}}" class="card-img-top" alt="{{title}}"  style="max-width: 200px;">
                            {{/thumbnail}}
                            <p class="card-text">{{{description}}}</p>
                            <a href="{{{link}}}" target="_blank" class="btn btn-secondary">자세히 보기</a>

                        </div>
                    </div>
                </div>
            {{/items}}
        </div>
    {{/res}}

    {{^res}}
        <p>검색 결과가 없습니다.</p>
    {{/res}}
</div>

    <div id="searchResults"></div>

</div>
{{>layouts/footer}}

 

끝.

 

이번 예시에서는 내가 뷰를 만들어야해서 객체로 변환한 후 model.addAttribute()로 값을 전달했는데, 이것이 비효율적인것 같다는 생각이 들었다.

(네이버API에서 JSON으로 잘 포장해서 보내줬는데 내가 중간에서 다 풀어헤치고 다시 포장해서 보낸것 같음)

 

REST API를 설계하는것이라면 객체로 변환하지 않고 JSON 그대로 전달해도 될 것 같다.

728x90
반응형

댓글