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 개선 방법
- 페이지 로드 후 스크롤하는 과정 생략
- 이미지 개수를 핀터레스트 첫 줄 이미지 개수인 7개로 제한
- 검색 과정 최적화
기존 : 핀터레스트 홈페이지 접속 후 좌측 상단 탐색 버튼을 눌러야 검색창이 생겨서 탐색 버튼을 누른 뒤에 검색창에 키워드를 넣고 검색할 수 있었음. 따라서 크롤링 과정에 탐색 버튼을 누르고, 검색창에 키워드 입력 후 엔터를 치는 과정이 포함되어 검색 과정이 길었음.
수정 : 핀터레스트 홈페이지 접속 시
https://kr.pinterest.com/
로 접속하는 것이 아닌, 키워드 검색어까지 같이 넣어"https://kr.pinterest.com/search/pins/?q=" + URLEncoder.encode(keyword, StandardCharsets.UTF_8)
로 검색 과정 생략
- 랜덤 이미지 선택 로직 최적화
- 기존 :
stream().skip(random.nextInt(imageUrls.size())).findFirst()
를 사용해 반복문이 돌아 시간 오래 걸림 - 수정 :
(Collections.shuffle())
사용해 해결
- 기존 :
3-4 성능 비교
🔥 총 22초, 78.57% 향상🔥
4. 실행 테스트
4-1 스웨거 테스트
)
4-2 로그 확인
- keyword : 남성 오버사이즈 블레이저
- 핀터레스트 url : https://kr.pinterest.com/search/pins/?q=%EB%82%A8%EC%84%B1+%EC%98%A4%EB%B2%84%EC%82%AC%EC%9D%B4%EC%A6%88+%EB%B8%94%EB%A0%88%EC%9D%B4%EC%A0%80
- 이미지 url : https://i.pinimg.com/236x/b8/eb/2f/b8eb2f4a0e6c3c83032508c3f8986c1b.jpg
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. 참고
'mody' 카테고리의 다른 글
도메인 구매하여 Certbot을 통해 HTTPS 배포하기 (0) | 2025.03.09 |
---|---|
S3 파일 업로드 CORS 해결 (0) | 2025.03.09 |
스프링에서 S3 이미지 삭제 deleteObject() 403 Access Denied - AWSCompromisedKeyQuarantineV3 정책 (0) | 2025.03.09 |
Spring Boot에서 OpenAI API 사용에 프롬프트 최적화하기 (0) | 2025.03.09 |
mody - 당신의 AI 스타일 친구 (0) | 2025.03.09 |