문제발생

이전 글에서 config 파일로 리소스 핸들러를 추가해 이미지 렌더링하는데에는 성공했다.

하지만 이전에 만들었던 SeleniumConfig 도 그런데, config 파일을 이렇게 사용하면 아래와 같은 문제가 있었다.

  • 보안에 민감한 값을 포함할 수 있다.
  • OS 별로 다른 변수를 이용해야할 수 있다.

 

문제해결

현재 프로젝트도 윈도우와 맥에서 사용하는 변수값이 달라서 고민이었고, 환경변수를 yml 파일로 분리하기로 했다.

방식은 이 글에서 썼던 @ConfigurationProperties 를 사용하는 방식을 채택했다.

과정을 요약하면 아래와 같다.

 

  1. yml 파일에 환경변수를 설정한다.
  2. @ConfigurationProperties 및 @ConstructorBinding 으로 yml 의 값을 바인딩한다.
  3. Application 클래스에 @EnableConfigurationProperties 로 해당 config 파일을 빈으로 활성화 한다.

1. yml 파일에 환경변수를 설정한다.

application.yml

selenium:
  driver-name: "driver/chromedriver.exe" 
  // 윈도우 기준임. 맥은 driver/chromedriver

resource:
  absolute-path: "file:///C:/Users/midcon/Desktop/my-molu-be/download/"
  // 윈도우 기준임. 맥은 "Users/midcon/Desktop/my-molu-be/download/"

 

2. @ConfigurationProperties 으로 yml 의 값을 바인딩한다.

변경전

WebConfig

@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {

    private final static String ABS = "\\C:\\Users\\midcon\\Desktop\\my-molu-be\\download\\";

    private final CrawlingService crawlingService;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        Path imageAbsolutePath = Paths.get(crawlingService.DOWNLOAD_DIRECTORY).toAbsolutePath();
        // localhost:8080/images/image.jpg
        registry.addResourceHandler("/download/**")
            .addResourceLocations("file://" + ABS);
    }
}

SeleniumConfig

@Configuration
public class SeleniumConfig {

    public ChromeDriver chromeDriver() {
        System.setProperty("webdriver.chrome.driver", "chormedriver.exe");
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--remote-allow-origins=*");
        ChromeDriver driver = new ChromeDriver(options);
        driver.manage().timeouts().implicitlyWait(Duration.ofMinutes(3));
        driver.manage().window().maximize();
        return driver;
    }
}

 

변경후

WebConfig

@RequiredArgsConstructor
@ConstructorBinding
@ConfigurationProperties(prefix = "resource")
public class WebConfig implements WebMvcConfigurer {

    private final String absolutePath;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // localhost:8080/images/image.jpg
        registry.addResourceHandler("/download/**")
            .addResourceLocations(absolutePath);
    }
}

SeleniumConfig

@ConstructorBinding
@RequiredArgsConstructor
@ConfigurationProperties(prefix = "selenium")
public class SeleniumConfig {

    private final String driverName;

    public ChromeDriver chromeDriver() {
        System.setProperty("webdriver.chrome.driver", driverName);
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--remote-allow-origins=*");
        ChromeDriver driver = new ChromeDriver(options);
        driver.manage().timeouts().implicitlyWait(Duration.ofMinutes(3));
        driver.manage().window().maximize();
        return driver;
    }
}

 

 

3. @EnableConfigurationProperties 로 config 파일을 빈으로 등록한다.

MyMoluApplication

@EnableConfigurationProperties(value = {SeleniumConfig.class, WebConfig.class})
@SpringBootApplication
public class MyMoluApplication {

   public static void main(String[] args) {
      SpringApplication.run(MyMoluApplication.class, args);
   }
}

 

이제 윈도우에서와 맥에서 yml 파일 설정만 바꿔주면 나머지 파일들은 모두 깃허브로 관리할 수 있다.

이전처럼 SeleniumConfig 파일을 .gitignore 에 등록할 필요가 없어졌으니 제거하면 된다.

이처럼 민감한 값이나 개발환경에 따라 다른 값을 사용해야할 경우 yml 파일로 환경변수를 관리하면 된다.

문제발생

야심차게 셀레니움으로 크롤링 기능을 만들고 HTML, JS 로 페이지도 만들고 CORS 문제도 해결했다.

테스트로 크롤링이 잘 되는지도 확인했고, 이미지도 잘 저장했고 응답값도 확인했으니 이제 렌더링만 하면 될 줄 알았다.

하지만 어떤 이미지는 보이고, 어떤 이미지는 불러오지 못하는 현상이 발생했다.

 

문제해결

문제 원인을 찾기 위한 여러가지 시도 끝에 안보이던 이미지는 서버 내렸다 다시 올리면 뜨는걸 보았다.

그래서 빌드 이전에 저장된 이미지는 잘 보이는데 빌드 이후 저장한 이미지는 불러오지 못하는거란 생각이 들었다.

이를 통해 스프링 동작 원리를 간단히 찾아보았다.

  • 스프링에서 정적 리소스는 빌드 타임에 결정된다.
  • 이미지 파일은 기본적으로 resource/static 을 root 로 인식하고 불러온다.
  • static 폴더에 빌드 이후 런타임에 동적으로 추가된 리소스는 기본적으로 인식하지 않는다.

 

따라서 리소스 핸들러를 적용하여 static 폴더 외부에서 이미지를 불러오도록 설정해줘야한다.

아래처럼 WebMvcConfigurer 에 리소스 핸들러를 추가해주면 된다.

WebConfig

경로 변수들을 yml 로 따로 빼서 환경변수 설정을 하여 사용하면 더 좋다.

@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {

    private final static String ABS = "\\C:\\Users\\midcon\\Desktop\\my-molu-be\\download\\";

    private final CrawlingService crawlingService;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        Path imageAbsolutePath = Paths.get(crawlingService.DOWNLOAD_DIRECTORY).toAbsolutePath();
        // localhost:8080/images/image.jpg
        registry.addResourceHandler("/download/**")
            .addResourceLocations("file://" + ABS);
    }
}

 

 

문제발생

yml 파일로 환경변수를 분리하려고 했다.

@Configuration
@ConstructorBinding
@RequiredArgsConstructor
@ConfigurationProperties(prefix = "selenium")
public class SeleniumConfig {

    private final String driverName;

    public ChromeDriver chromeDriver() {
        System.setProperty("webdriver.chrome.driver", driverName);
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--remote-allow-origins=*");
        ChromeDriver driver = new ChromeDriver(options);
        driver.manage().timeouts().implicitlyWait(Duration.ofMinutes(3));
        driver.manage().window().maximize();
        return driver;
    }
}

@ConfigurationProperties 로 yml 값을 불러오고 @ConstructorBinding 으로 값도 바인딩 했다.

이제 Application 클래스로 가서 @EnableConfigurationProperties 로 config 파일만 설정해주면 되겠지 싶었다.

하지만 Application 클래스의 설정을 해주니 아래와 같은 에러창이 나를 맞아주었다.

 

 

문제해결

@Configuration 때문에 발생한 에러였다.

 Application 클래스로의 @EnableConfigurationProperties config 파일의 빈 등록을 해주기 때문에

@Configuration 을 달면 위와 같은 오류가 발생하는 것이었다.

그러므로 아래처럼 @Configuration 을 빼주면 정상 동작한다.

@ConstructorBinding
@RequiredArgsConstructor
@ConfigurationProperties(prefix = "selenium")
public class SeleniumConfig {

    private final String driverName;

    public ChromeDriver chromeDriver() {
        System.setProperty("webdriver.chrome.driver", driverName);
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--remote-allow-origins=*");
        ChromeDriver driver = new ChromeDriver(options);
        driver.manage().timeouts().implicitlyWait(Duration.ofMinutes(3));
        driver.manage().window().maximize();
        return driver;
    }
}

 

 

문제발생

야심차게 백엔드 서버에서 크롤링 기능을 만들고 화면에 렌더링하려고 fetch 함수를 써서 데이터를 받아오려 했다.

그래서 백엔드 서버를 띄우고 localhost:8080 에 fetch 함수로 요청을 보냈다.

하지만 요청을 보내면 200으로 응답은 받는데 자꾸 응답 body가 빈값으로 오는 현상이 발생했다.

 

문제해결

원인은 CORS 때문이었다. CORS의 개념은 이 글을 참고하자.

해결 방법은 아래의 두가지가 생각이 났다.

  1. 백엔드 서버에서 origin 관련 설정을 설정해준다.
  2. 백엔드 서버 자체에서 HTML 을 렌더링해서 origin 이 애초에 같도록 한다.

스프링으로 HTML, JS 을 사용해본적이 없어서 경험해보고 싶었기 때문에 이 중 두번째 방법을 선택했다.

예상대로 origin이 같으니 응답 body에 데이터가 제대로 넘어왔다.

프론트를 만져보니 팀 프로젝트때는 대충 설정하고 넘겼던 CORS 라는 개념을 직접 사용해야해서

설정에 대해 더 공부하고, 이해하고 알아보는 좋은 기회가 됐다.

하지만 웹서버와 WAS 를 분리하는 시점이 오면 첫 번째도 해봐야 할 시점이 오겠지.

그땐 조금 더 많은 경험을 할 수 있을것 같다.

문제 발견

이전 글에서 윈도우와 맥에서의 크롬 드라이버 설정 문제를 만났다.

맥에서 크롬 드라이버 사용 문제는 해결 했지만, 또 다른 문제가 아직 남아있다.

집에서는 데스크탑으로 작업하니 윈도우 환경이고, 밖에서는 맥북으로 작업하니 맥 환경이다. 

깃허브로 프로젝트 관리를 하는데 크롬 드라이버도 다르고, 설정 부분도 달라서 애로사항이 꽃핀다.

 


문제 해결

해결법은 두가지가 떠올랐다.

  1. 도커를 사용하여 어디서든 동일한 개발 환경을 만든다.
  2. 설정 부분을 config 클래스로 분리하고 gitignore 파일에 config 와 크롬 드라이버를 추가하여
    설정 차이가 생기는 부분은 따로 설정하고, 이외에 다른 부분들만 깃허브로 프로젝트를 관리하여 해결한다.

1번이 사실 제일 좋은 방법일 수도 있지만, 내가 선택한 방법은 2번이다.

우선 도커는 써본적이 없기 때문에 공부에는 시간이 들거라고 생각했다.

그래서 새로운 기술의 공부보다는 프론트, 백엔드 부분 완성 및 배포를 우선시 하기로 했다.

언제까지고 2번처럼 할 수는 없을 것이니 우선 목표 달성부터 하고 도커 공부는 그 뒤에 해볼 것이다.

CrawlerConfig

크롬 드라이버 초기화 관련 메서드를 config 클래스로 옮기고, 초기화 된 크롬 드라이버를 빈 등록한다.

import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;

@Configuration
public class CrawlerConfig {

    @Bean
    public ChromeDriver chromeDriver() {
        System.setProperty("webdriver.chrome.driver", "driver/chromedriver.exe");
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--remote-allow-origins=*");
        ChromeDriver driver = new ChromeDriver(options);
        driver.manage().timeouts().implicitlyWait(Duration.ofMinutes(3));
        driver.manage().window().maximize();
        return driver;
    }
}

Crawler

크롬 드라이버를 빈 등록했으니 스프링이 자동으로 의존성 주입을 해준다.

@RequiredArgsConstructor
public class Crawler {

    // 크롬 드라이버 의존성 주입
    private final ChromeDriver driver;
    
    // ...크롤링하는 코드... //
    
}

.gitignore

윈도우와 맥에서 설정 방법 차이가 있는 부분을 gitignore 에 추가 한다.

// 아래 항목들 추가
CrawlerConfig.class  // CrawlerConfing 제외
/driver/             // driver 폴더의 모든 파일 제외

문제 발견

윈도우, 맥 두집 살림을 하다가 발생한 크롬 드라이버 관련 문제이다.

딱히 자바 스프링에 한정 된 문제는 아니고 mac OS 문제라서 어떤 언어로 셀레니움을 사용하든 발생하는 문제인듯 하다.

윈도우에서 셀레니움 사용 시 ChromeDriver.exe 를 아래처럼 사용하면 됐다.

private static ChromeDriver initDriver() {
    System.setProperty("webdriver.chrome.driver", "driver/chromedriver.exe");
    ChromeOptions options = new ChromeOptions();
    options.addArguments("--remote-allow-origins=*");
    ChromeDriver driver = new ChromeDriver(options);
    driver.manage().timeouts().implicitlyWait(Duration.ofMinutes(3));
    driver.manage().window().maximize();
    return driver;
}

 

하지만 맥에서 셀레니움 사용 시 두가지 문제점이 있다.

  1. 크롬 드라이버의 확장자가 없어서 수정해줘야 한다.
  2. 경고창이 뜨면서 크롬 드라이버를 사용할 수 없다.

 


문제 해결

1. 크롬 드라이버의 확장자가 없어서 아래처럼 수정해줘야 한다.

private static ChromeDriver initDriver() {
    System.setProperty("webdriver.chrome.driver", "driver/chromedriver");
    ChromeOptions options = new ChromeOptions();
    options.addArguments("--remote-allow-origins=*");
    ChromeDriver driver = new ChromeDriver(options);
    driver.manage().timeouts().implicitlyWait(Duration.ofMinutes(3));
    driver.manage().window().maximize();
    return driver;
}

 

2. 경고창이 뜨면서 크롬 드라이버를 사용할 수 없다.

아래와 같은 경고창이 뜬다.

 

 

이래서 애플이 보안에 강하다고 하나보다 아님말고.

아무튼 이런 보안 관련 문제가 생기기 때문에 크롬 드라이버가 있는 경로로 아래 명령어를 입력해서 강제로 실행해야한다.

나는 프로젝트 폴더 하위 driver 폴더에 크롬 드라이버를 뒀으니 프로젝트 폴더에서 아래와 같은 명령어를 입력했다.

// xattr -d com.apple.quarantine 뒤에 크롬 드라이버 경로를 입력하면 된다.

xattr -d com.apple.quarantine dirver/chromedriver

 

두집 살림을 하다보니 슬슬 도커의 필요성이 느껴지는것 같기도 하다.

도커도 써보면서 천천히 연습해봐야겠다.

문제 발견

픽시브 크롤링을 하면서 예상대로라면 60장의 이미지 url 을 긁어와야했다.

하지만 아무리 해도 일부만 긁어오고 모두 긁어오지 않았다.

CSS 셀렉터 경로는 제대로 넣은것 같은데 아무리 해도 원하는만큼 크롤링을 해오지 않았다.

 

 

그나마 아래 코드에서 implicitlyWait 으로 암묵적인 대기 시간을 걸어주었기 때문에 바로 창이 닫히지 않았지

이 설정이 없었으면 3분의 대기시간 없이 바로 NoSuchElement 행이었다.

 

private static ChromeDriver initDriver() {
    System.setProperty("webdriver.chrome.driver", "driver/chromedriver.exe");
    ChromeOptions options = new ChromeOptions();
    options.addArguments("--remote-allow-origins=*");
    ChromeDriver driver = new ChromeDriver(options);
    driver.manage().timeouts().implicitlyWait(Duration.ofMinutes(3)); // 암시적 대기
    return driver;
}

 

아래처럼 긁어오다가 만다.

 

 


문제 해결

픽시브는 정적인 페이지가 아닌 동적인 페이지였기 때문에 발생한 오류였다. 우선 아래처럼 ChromeDriver 는 작은 창으로 뜬다.픽시브는 마우스 위치에 따라 동적으로 데이터를 받아오는 방식이다.즉, 아래같은 상태에서는 기본 10개 정도의 이미지만 받아오고 나머지 50개는 스크롤을 내릴 때 받아온다.

 

 

그래서 스크롤을 내려주면 제대로 60개의 이미지를 받아옴을 알 수 있다.

 

 

이제 셀레니움의 기능을 이용하여 마우스 관련 자바스크립트 이벤트를 실행하면

ChromeDriver 를 켜고 자동으로 스크롤을 내려서 원하는 이미지를 받아올 수 있다.

+ Recent posts