문제 발견

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

딱히 자바 스프링에 한정 된 문제는 아니고 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 를 켜고 자동으로 스크롤을 내려서 원하는 이미지를 받아올 수 있다.

문제 발견

외부 API 에서 데이터를 받아와서 호출하는 페이지를 만들려고 했다.

버튼을 누르기 전에는 아무것도 렌더링하지 않고, fetch 함수를 호출할 때 데이터를 렌더링하게 만들고 싶었다.

그림으로 나타내면 아래와 같다.

하지만 버튼을 한번 누르면 데이터가 렌더링 되지 않고 두번 이상 눌러야 렌더링 되는 문제가 발생했다.

 


문제 해결

fetch 함수가 promise 객체를 반환하는 함수이기 때문에 발생한 문제였다.

promise 객체는 비동기 처리를 위해 나온 객체이며 코드 실행 순서는 순서대로 진행하다가

비동기 함수(promise 객체를 반환하는 함수)가 실행되면 즉시 다음 코드로 실행 흐름이 넘어간 후 나중에 콜백이 실행된다.

console.log('Start');

fetch(url)
  .then((response) => response.json())
  .then((result) => { console.log("fetch"); });

console.log('End');

 

위의 코드는 동기 실행의 경우 아래처럼 출력 됐을 것이다.

 

  1. console.log("start") 출력
  2. fetch 함수를 실행하여 해당 url로 요청
  3. fetch의 응답값을 받고 then 메서드로 콜백 수행하여 consol.log("fetch") 출력
  4. console.log("End") 출력

하지만 비동기 실행의 경우 아래처럼 동작한다.

 

  1. console.log("start") 출력
  2. fetch 함수를 실행하여 해당 url로 요청
  3. console.log("End") 출력
  4. fetch의 응답값을 받고 then 메서드로 콜백 수행하여 console.log("fetch") 출력

변경 전

const url = "https://api-blue-archive.vercel.app/api/characters?page=2";
const targetArea = document.querySelector('.targetArea');
const imageArea = document.querySelector('.imageArea');
const btn = document.querySelector('.magicBtn');

let bindedData = {};

getData = function () {
    fetch(url, {
        mode: 'cors'})
        .then((response) => response.json())
        .then((data) => {
            console.log("fetch finished");
            return bindedData = data.data;
        });
    imageArea.innerHTML = '';
    console.log("rendering start");
    console.log(bindedData);
    for (let i = 0; i < 20; i++) {
        const student = document.createElement('div');
        const name = document.createElement('div');
        name.textContent = bindedData[i].name;
        student.append(name);
        const image = document.createElement('img');
        image.setAttribute('src',bindedData[i].photoUrl);
        student.append(image);
        imageArea.append(student);
    }
}

btn.addEventListener('click', getData);

콘솔창은 아래와 같다. (버튼 두번 눌러야 원하는 응답이 온다. 빨간 줄 기준으로 1번째 2번째 클릭 구분)

 

변경 후

const url = "https://api-blue-archive.vercel.app/api/characters?page=2";
const targetArea = document.querySelector('.targetArea');
const imageArea = document.querySelector('.imageArea');
const btn = document.querySelector('.magicBtn');

let bindedData = {};

getData = function () {
    fetch(url, {
        mode: 'cors'})
        .then((response) => response.json())
        .then((data) => {
            console.log("fetch finished");
            imageArea.innerHTML = '';
            bindedData = data.data;
            
            console.log("rendering start");
            console.log(bindedData);
            for (let i = 0; i < 20; i++) {
                const student = document.createElement('div');
                const name = document.createElement('div');
                name.textContent = bindedData[i].name;
                student.append(name);
                const image = document.createElement('img');
                image.setAttribute('src',bindedData[i].photoUrl);
                student.append(image);
                imageArea.append(student);
            }
        });
}

btn.addEventListener('click', getData);

콘솔창은 아래와 같다. (버튼 한번만 눌러도 원하는 응답을 받을 수 있다.)

 

+ Recent posts