본문 바로가기

[SpringBoot] 네이버 블로그 연동해서 최근 게시글 + 썸네일 가져오기

@suhyeon chae2025. 8. 12. 10:47

 

 

                        서론                        

네이버 블로그 최신 게시글 5개와 썸네일을 가져올 수 있도록 구현했다. 정말 간단하다!!!

 

단순히 게시글 제목, 썸네일, 발행 일자 정도만 가져와도 된다면 이 글을 참고해도 될 것 같다.

 

간단한 정보만 가져와도 된다면 api까지 연동할 필요 없다. (현재 공식적으로 제공되는 네이버 블로그 api 없음..) 

대신 네이버 블로그는 RSS 방식을 제공한다.

 

 

 

                        RSS 란?                        

Really Simple Syndication의 약자이며,

웹사이트가 새로 게시물을 올리거나 업데이트 했을 때 그 내용을 자동으로 요약해서 전달하는 표준 포맷이다.

주로 XML 형식으로 작성되어 있다

 

글 작성부터 사용자게에 보여지기까지 크게는 아래의 4단계로 진행이 된다.

 

1. 블로그 운영자가 글을 작성한다.

2. 네이버 서버에서 RSS XML 파일을 갱신한다.

3. 외부 서비스에서 해당 XML 파일을 읽는다.

4. 사용자에게 보여준다.

 

 

 

                       사전 준비                       

 

사전에 알아둬야할 것은 내가 가져올 게시글이 있는 블로그의 xml 파일의 주소이다.

지금은 운영을 안하는... ㅋㅋ 나의 네이버 블로그를 예시로 하자면, https://blog.naver.com/ddd8177 

 

A LI- 심심한 대학생 블로그 : 네이버 블로그

심심한 대학교 4학년 일상♬

blog.naver.com

 

네이버 블로그 주소 뒤에 붙은 아이디를 가져와서

https://blog.rss.naver.com/ + 아이디.xml 을 해주면 준비 끝이다... ㅋㅋㅋ

 

ex) https://blog.rss.naver.com/ddd8177.xml

 

해당 주소를 주소창에 입력하면 아래와 같이 xml 파일이 보인다.

어지럽겠지만 차근차근 읽어보면 실제 내 블로그 게시글에 대한 정보가 나온다.

 

 

 

 

                       코드 구현                       

 

 * BlogPostDto

먼저 반환 받을 Dto를 만들었다.

@Getter
@Setter
public class BlogPostDto {
    private String title;
    private String url;
    private String thumbnail;

    public BlogPostDto(String title, String url, String thumbnail) {
        this.title = title;
        this.url = url;
        this.thumbnail = thumbnail;
    }
}

 

 

* 네이버 블로그 호출 코드 (service)

api로 제공해야하는 경우 해당 메서드에서 반환받은 내용을 Controller에서 그대로 반환하면 된다. 

나 같은 경우는 jsp를 사용했기 때문에 api로 제공하지않고 반환반은 List<BlogPostDto> 를 model에 담아서 넘겨줬다 (해당 코드는 없음)

 

/**
* 네이버 블로그 호출
*/
public List<BlogPostDto> getLatestPosts() {
    String RSS_URL = "https://blog.rss.naver.com/ddd8177.xml"; // 사전에 준비한 xml 파일 주소

    List<BlogPostDto> posts = new ArrayList<>(); // 게시글 5개 가져오기 위해 선언
    try {
        // RSS XML 다운로드 & 파싱
        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
        Document doc = dBuilder.parse(new URL(RSS_URL).openStream());
        doc.getDocumentElement().normalize();

        NodeList items = doc.getElementsByTagName("item");
        int limit = Math.min(5, items.getLength()); // 게시글이 5개가 안될 수도 있기 때문에 최솟값으로 구해줌

        for (int i = 0; i < limit; i++) {
            Element item = (Element) items.item(i);

            String title = getTagValue("title", item);
            String link = getTagValue("link", item);
            String description = getTagValue("description", item); // 태그에 맞는 내용으로 파싱

            String thumbnailUrl = extractThumbnail(description); // 썸네일 주소 추출
            posts.add(new BlogPostDto(title, link, thumbnailUrl));
     	}
    } catch (Exception e) {
        e.printStackTrace();
    }
    return posts;
}

 

 

* 태그 내용 추출, 썸네일 url 추출

/**
 *	네이버 블로그 태그 내용 추출하기
 */
private String getTagValue(String tag, Element element) {
    NodeList nodeList = element.getElementsByTagName(tag);
    if (nodeList.getLength() > 0 && nodeList.item(0).getFirstChild() != null) {
        return nodeList.item(0).getFirstChild().getNodeValue();
    }
    return null;
}

/**
 *	네이버 블로그 썸네일 추출
 */
private static String extractThumbnail(String html) {
    if (html == null) return null;
    Pattern imgSrcPattern = Pattern.compile(
            "<img[^>]+src\\s*=\\s*['\"]([^'\"]+)['\"]", Pattern.CASE_INSENSITIVE);
    Matcher matcher = imgSrcPattern.matcher(html);
    if (matcher.find()) {
        return matcher.group(1); // 첫 번째 src 속성 값
    }
    return null;
}

 

 

 

                       문제점                       

로컬에서는 잘 동작했으나... 서버에 올리니 썸네일 이미지가 403 Rorbidden 에러를 내며 노출되지 않았다.

 

그 이유는

네이버 이미지 서버는 Referrer 헤더나 Origin이 허용된 도메인(네이버 자체 서비스 등)에서만 이미지를 보여주도록 설정되어 있다고 한다. 

 

 

 

 

                       해결 방법                        

네이버 썸네일 이미지를 프론트에서 바로 보여주는 것은 원칙적으로 불가능 하기 때문에

이미지 프록시 서버를 직접 구현하는 방법으로 진행을 해야한다.

 

이 방식은 Spring Boot 서버에서 해당 이미지를 백엔드가 대신 다운로드 하고 클라이언트에 다시 전달하는 방식이다.

단, 이미지 저작권/트래픽 부담 이슈가 있으니 실제 적용시에 많은 검토를 해봐야할 것 같다. (나는 트래픽에는 크게 부담이 없고 고객사 서비스에서 고객사 블로그의 게시글을 가져오는 터라 이미지 저작권에도 문제가 없어 진행했다.)

 

 

* proxy controller 생성

/**
 * 블로그 썸네일을 가져오기 위한 proxy 컨트롤러
 * 사용 이유
 *   rss 방식으로 블로그 내용을 가져오고 있으나, 썸네일의 경우 네이버 정책에 의해 특정 도메인에서 가져올 수가 없음
 *
 */
@RestController
@RequestMapping("/image-proxy")
public class ImageProxyController {

    private final RestTemplate restTemplate = new RestTemplate();

    @GetMapping
    public ResponseEntity<byte[]> proxyImage(@RequestParam String url) {
        ResponseEntity<byte[]> response = restTemplate.exchange(
                url, HttpMethod.GET, null, byte[].class);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(response.getHeaders().getContentType());

        return new ResponseEntity<>(response.getBody(), headers, HttpStatus.OK);
    }
}

 

* service의 getLatestPosts() 수정

하단의 ' 프록시 서버 사용하여 이미지 URL 가져오기 ' 주석 부분을 참고하면 될 것 같다.

/**
 * 네이버 블로그 호출
 */
public List<BlogPostDto> getLatestPosts() {
    String RSS_URL = "https://blog.rss.naver.com/ddd8177.xml";

    List<BlogPostDto> posts = new ArrayList<>();
    try {
        // RSS XML 다운로드 & 파싱
        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
        Document doc = dBuilder.parse(new URL(RSS_URL).openStream());
        doc.getDocumentElement().normalize();

        NodeList items = doc.getElementsByTagName("item");
        int limit = Math.min(5, items.getLength());

        for (int i = 0; i < limit; i++) {
            Element item = (Element) items.item(i);

            String title = getTagValue("title", item);
            String link = getTagValue("link", item);
            String description = getTagValue("description", item);

            String rawThumbnail = extractThumbnail(description);
            String proxyThumbnail = null;
            String baseUrl = "http://localhost:8080/";

            // 프록시 서버 사용하여 이미지 URL 가져오기
            if (rawThumbnail != null) {
                proxyThumbnail = baseUrl + "/image-proxy?url=" + URLEncoder.encode(rawThumbnail, String.valueOf(StandardCharsets.UTF_8));
            }

            posts.add(new BlogPostDto(title, link, proxyThumbnail));
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return posts;
}

 

 

 

suhyeon chae
@suhyeon chae :: 번아웃을 이겨내는중

신입 개발자 입니다 😃 github 주소 : https://github.com/chaesuhyeon

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차