본문 바로가기
mody

스프링에서 핀터레스트 이미지 크롤링하기

by seoshinehyo 2025. 3. 9.

1. flow

1-1 키워드 입력을 통해 OpenAI API로부터 스타일, 패션 아이템 추천 받아오기

1-2 사용자의 성별과 OpenAI API를 통해 받아 온 스타일, 패션 아이템에 맞게 keyword 생성

1-3 keyword를 통해 핀터레스트 페이지를 크롤링 해서 이미지 url 받아오기


2. 코드

2-1 build.gradle 의존성

// Selenium
    implementation 'org.seleniumhq.selenium:selenium-java:4.8.0'
  • Selenium 드라이버 매니저를 사용하기 위해 4.6.0+ 버전 사용 필요
  • 드라이버 매니저가 있으면 크롬 버전에 맞는 크롬 드라이버 자동으로 설치해줌

2-2 Dockerfile

FROM openjdk:21-jdk-slim

# 크롬 설치
RUN apt-get update && apt-get install -y wget curl unzip \
    && wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \
    && apt-get install -y ./google-chrome-stable_current_amd64.deb \
    && rm ./google-chrome-stable_current_amd64.deb \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*
  • 크롬 드라이버를 제외하고, 크롬 브라우저만 설치하기
  • Selenium 드라이버 매니저가 크롬 브라우저에 맞는 크롬 드라이버를 자동으로 설치해주기 때문

2-3 CrawlerService

@Slf4j
@Service
@RequiredArgsConstructor
public class CrawlerService {

    public String getRandomImageUrl(String keyword) {

        WebDriver driver = getWebDriver();
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));

        try {
            String searchUrl = "https://kr.pinterest.com/search/pins/?q=" + URLEncoder.encode(keyword, StandardCharsets.UTF_8);
            driver.navigate().to(searchUrl);
            log.info("Pinterest 검색 URL 접속 완료: {}", searchUrl);

            // 이미지 태그 검색 결과 로드
            wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath("//img[contains(@src, 'https')]")));
            log.info("이미지 태그 검색 결과 로드 완료");

            Set<String> imageUrls = new HashSet<>();
            List<WebElement> images = driver.findElements(By.xpath("//img[contains(@src, 'https')]"));

            int maxImages = Math.min(images.size(), 7); // 최대 이미지 7개(핀터레스트 첫 줄 사진이 7개)
            for (int i = 0; i < maxImages; i++) {
                String url = images.get(i).getAttribute("src");
                if (url != null && !url.isEmpty()) {
                    imageUrls.add(url);
                }
            }

            if (imageUrls.isEmpty()) {
                log.error("이미지 URL을 찾을 수 없음 (키워드: {})", keyword);
                throw new RestApiException(CrawlerErrorStatus.IMAGE_NOT_FOUND);
            }

            List<String> imageList = new ArrayList<>(imageUrls);
            Collections.shuffle(imageList);
            return imageList.get(0);

        } catch (TimeoutException e) {
            log.error("페이지 로드 실패 (키워드: {})", keyword);
            throw new RestApiException(CrawlerErrorStatus.PAGE_LOAD_ERROR);
        } catch (Exception e) {
            log.error("크롤링 실패 (키워드: {}):", keyword);
            throw new RestApiException(CrawlerErrorStatus.CRAWLING_FAILED);
        } finally {
            driver.quit();
        }
    }

    private WebDriver getWebDriver() {
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--headless"); // 백그라운드 실행 (UI 렌더링 생략)
        options.addArguments("--disable-gpu"); // GPU 사용 X
        options.addArguments("--no-sandbox"); // 샌드박스 모드 비활성화(Docker 환경에서 크롬 드라이버 실행에 필요)
        options.addArguments("--disable-dev-shm-usage"); // /dev/shm 사용 비활성화(Docker 환경에서 크롬 크래시 문제 해결)
        options.addArguments("--ignore-ssl-errors=yes");
        options.addArguments("--ignore-certificate-errors"); // SSL 차단 대비

        return new ChromeDriver(options);
    }
}
  • 크롬 드라이버에 필요한 옵션 설정
  • OpenAI API에서 넘어오는 스타일, 패션 아이템에 맞게 생성된 keyword를 받음
  • keyword를 통해 핀터레스트 검색 페이지로 접속
  • img 태그를 통해 이미지 7개 크롤링 후 랜덤으로 1개의 이미지 url 반환

3. 시간 개선

3-1 기존 : 스타일 분석 33초(OpenAI API 통신 5초 / 크롤링 28초)

3-2 개선 후 : 스타일 분석 11초(OpenAI API 통신 5초 / 크롤링 6초)

3-3 개선 방법

  1. 페이지 로드 후 스크롤하는 과정 생략
  2. 이미지 개수를 핀터레스트 첫 줄 이미지 개수인 7개로 제한
  3. 검색 과정 최적화

  • 기존 : 핀터레스트 홈페이지 접속 후 좌측 상단 탐색 버튼을 눌러야 검색창이 생겨서 탐색 버튼을 누른 뒤에 검색창에 키워드를 넣고 검색할 수 있었음. 따라서 크롤링 과정에 탐색 버튼을 누르고, 검색창에 키워드 입력 후 엔터를 치는 과정이 포함되어 검색 과정이 길었음.

  • 수정 : 핀터레스트 홈페이지 접속 시 https://kr.pinterest.com/ 로 접속하는 것이 아닌, 키워드 검색어까지 같이 넣어 "https://kr.pinterest.com/search/pins/?q=" + URLEncoder.encode(keyword, StandardCharsets.UTF_8) 로 검색 과정 생략

  1. 랜덤 이미지 선택 로직 최적화
    • 기존 : stream().skip(random.nextInt(imageUrls.size())).findFirst() 를 사용해 반복문이 돌아 시간 오래 걸림
    • 수정 : (Collections.shuffle()) 사용해 해결

3-4 성능 비교

🔥 총 22초, 78.57% 향상🔥


4. 실행 테스트

4-1 스웨거 테스트

)

4-2 로그 확인


5. Trouble Shooting

로컬 환경에서는 크롤링이 잘 되는데, 배포 환경에서 크롤링이 안 되는 문제

5-1 문제 상황1 && 해결 방법

  • 문제 상황

    • Selenium 4.6.0+ 버전부터는 직접 크롬 드라이버 다운로드 필요 없이, 자체 드라이버 매니저를 통해 크롬 드라이버를 다운로드 함

    • 크롬 드라이버를 다운로드 할 서버 디바이스 용량 부족

      -> 6.5G / 6.8G 97% 사용 중

  • 해결 방법

    • 서버에 불필요한 도커 이미지 및 파일들 삭제

    • EBS 볼륨 8GB -> 16GB로 스케일업

      -> 45% 사용으로 널널해져서 용량 부족 해결 완료

5-2 문제 상황2 && 해결 방법

  • 문제 상황

    • 서버에 크롬을 설치했는데, Selenium 드라이버 매니저가 크롬 드라이버를 설치하지 못 함

    • An error occurred: Could not start a new session. Possible causes are invalid address of the remote server or browser start-up failure 의 에러 메시지로, SessionNotCreatedException 이 터짐

    • 컨테이너 bash 환경이 아닌 서버 ssh에 크롬을 설치하는 게 문제였음

  • 해결 방법

    • 도커 파일에서 컨테이너에 크롬을 설치하는 로직 추가

    • 컨테이너 안에 크롬을 설치하니 이미지 크기가 615MB -> 1.43GB로 증가하긴 함

      -> 컨테이너 bash에 크롬과 크롬 버전에 맞는 크롬 드라이버 설치됨

RUN apt-get update && apt-get install -y wget curl unzip \
    && wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \
    && apt-get install -y ./google-chrome-stable_current_amd64.deb \
    && rm ./google-chrome-stable_current_amd64.deb \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

5-3 문제 상황3 && 해결 방법

  • 문제 상황

    • 도커 파일에서 크롬 브라우저 설치 과정에서 빌드 오류가 생김
    • 기존 jdk로 openjdk:21 을 사용하는 게 문제였음
    • openjdk:21 가 Oracle Linux 계열이라 apt-get 명령어가 먹지 않음
  • 해결 방법

    • 데비안 계열의 openjdk:21-jdk-slim 로 jdk 변경
    • apt-get 명령어가 실행되어 컨테이너 속에 크롬 브라우저 정상적으로 설치 후 크롬 드라이버도 잘 설치됨

6. 참고

  1. https://velog.io/@shzero211/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8%EC%97%90%EC%84%9C-%EC%85%80%EB%A6%AC%EB%8B%88%EC%9B%80%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

  2. https://djlife.tistory.com/m/21?category=1068870

  3. https://velog.io/@cobin_dev/%ED%81%AC%EB%A1%AC-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84-%EC%85%80%EB%A0%88%EB%8B%88%EC%9B%80-%EB%8F%84%EC%BB%A4-%ED%99%98%EA%B2%BD-%EC%97%B0%EB%8F%99

  4. https://coor.tistory.com/65

  5. https://www.selenium.dev/documentation/webdriver/troubleshooting/errors/driver_location/