픽시브 짤 다운로더

referer 헤더의 벽을 넘지 못하고 픽시브 짤 다운로더로 노선을 변경했다.

그래도 이미지 url 을 이용해서 다운로드 하니까 이전에 했던게 헛된건 아니라고 할 수 있겠다.

기존의 이미지 url 긁어오는 로직에서 다운로드 정도만 추가했다.

대략적인 과정은 아래와 같다.

 

  1. 셀레니움을 이용하여 이미지 url을 크롤링 한다.
  2. 크롤링해온 이미지 url 을 이용하여 referer 헤더 설정 후 이미지 다운로드 한다.
  3. 픽시브 몰루 일러스트 탭의 1페이지 60장을 저장한다.
  4. 저장한 이미지 파일 이름을 리턴 값으로 반환한다.
  5. 이미지 저장은 try with resources 를 이용하므로 따로 스트림을 닫을 필요는 없다.

CrawlingService

테스트는 이미지 url 만 긁어오는것과 거의 달라진게 없으므로 따로 올리지는 않겠다.

이미지 다운로드 경로는 src/main/resources/static/download 로 했다.

@RequiredArgsConstructor
@Service
public class CrawlingService {

    // 짤 크롤링
    private static final String URL_ILLUSTRATION = "https://www.pixiv.net/tags/%E3%83%96%E3%83%AB%E3%83%BC%E3%82%A2%E3%83%BC%E3%82%AB%E3%82%A4%E3%83%96/illustrations";
    private static final String IMAGE_BOX_ILLUSTRATION = "#root > div.charcoal-token > div > div:nth-child(4) > div > div > div.sc-15n9ncy-0.jORshO > section > div.sc-l7cibp-0.juyBTC > div:nth-child(1) > ul > li";
    private static final String DOWNLOAD_PATH = "src/main/resources/static/download/";


    public List<String> downloadImages() {
        ChromeDriver driver = SeleniumConfig.chromeDriver();
        driver.get(URL_ILLUSTRATION);

        autoScroll(driver);

        List<WebElement> imageBox = driver.findElements(By.cssSelector(IMAGE_BOX_ILLUSTRATION));
        List<String> images = imageBox.stream()
                .map(o -> o.findElement(By.cssSelector("img.sc-rp5asc-10.erYaF")).getAttribute("src"))
                .map(o -> {
                    String fileName = o.substring(o.lastIndexOf('/') + 1);
                    downloadImageWithReferer(o, DOWNLOAD_PATH + fileName);
                    return fileName;
                })
                .collect(Collectors.toList());

        driver.quit();
        return images;
    }

    private static void autoScroll(ChromeDriver driver) {
        Long height = (Long) driver.executeScript("return document.body.scrollHeight");
        Long scroll = 500l;
        while (scroll <= height) {
            driver.executeScript("window.scrollTo(0," + scroll + ")");
            scroll += 500l;
        }
    }

    private static void downloadImageWithReferer(String imageUrl, String destinationFile) {
        try {
            URL url = new URL(imageUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();

            // Referer 헤더 설정
            connection.setRequestProperty("Referer", "https://www.pixiv.net/");

            File folder = new File(DOWNLOAD_PATH);
            if (!folder.exists()) {
                folder.mkdir();
            }
            try (InputStream in = connection.getInputStream();
                 FileOutputStream out = new FileOutputStream(destinationFile)) {
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = in.read(buffer, 0, 1024)) != -1) {
                    out.write(buffer, 0, bytesRead);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 


다음 목표

다음 목표는 다운로드 받은 이미지를 렌더링하는 페이지 추가하는 것이다.

사실 이미지 다운로더로 노선 변경한것도 이미지 렌더링 페이지를 만들다가 난관에 봉착해서였다.

다시 원래 목표로 돌아가서 HTML, CSS, JS 도 써보면서 뭔가 보이는 페이지가 보이면 흥미도 더 붙고 좋을것 같다.

픽시브 짤 크롤링

셀레니움을 이용하여 픽시브 짤 크롤링 기능을 구현해보았다.

사실 크롤링이라 해야할지 스크래핑이라 해야할지 모르겠지만 아무튼...

이미지를 저장하는건 아니고 픽시브에서 일러스트 탭에서 블루아카이브 태그로 검색한 결과의 썸네일 리스트만 긁어온다.

픽시브는 동적 페이지라서 스크롤을 어느 정도까지 내려야 모든 이미지를 로딩한다.

하지만 이건 아직 미완성 버전이라 크롬 드라이버를 수동으로 스크롤해서 내려줘야 60장을 온전히 긁어온다.

크롤링 과정은 간단하게 설명하면 다음과 같다.

 

  1. 셀레니움을 이용하여 크롬 드라이버를 동작시킨다. (SeleniumConfig에서 관련 설정 초기화)
  2. 크롬 드라이버에 픽시브 몰루짤 검색 url 을 입력한다.
  3. 픽시브 몰루짤 일러스트 탭이 뜨고 썸네일 이미지 url 만 긁어온다 (60장.)

SeleniumConfig

윈도우 기준 config 이다. 맥은 조금 다르게 설정 해줘야 한다.

@Configuration
public class SeleniumConfig {

    public static 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;
    }
}

CrawlingService

@RequiredArgsConstructor
@Service
public class CrawlingService {

    // 짤 크롤링
    private static final String URL_ILLUSTRATION = "https://www.pixiv.net/tags/%E3%83%96%E3%83%AB%E3%83%BC%E3%82%A2%E3%83%BC%E3%82%AB%E3%82%A4%E3%83%96/illustrations";
    private static final String IMAGE_BOX_ILLUSTRATION = "#root > div.charcoal-token > div > div:nth-child(4) > div > div > div.sc-15n9ncy-0.jORshO > section > div.sc-l7cibp-0.juyBTC > div:nth-child(1) > ul > li";

    public List<String> getImages() {
        ChromeDriver driver = SeleniumConfig.chromeDriver();
        driver.get(URL_ILLUSTRATION);

        List<WebElement> imageBox = driver.findElements(By.cssSelector(IMAGE_BOX_ILLUSTRATION));
        List<String> images = imageBox.stream()
            .map(o -> o.findElement(By.cssSelector("img.sc-rp5asc-10.erYaF")).getAttribute("src"))
            .collect(Collectors.toList());

        driver.quit();
        return images;
    }
}

CrawlingServiceTest

테스트 코드는 간단하게 60장 다 긁어 오는지만 확인하는 테스트만 작성해봤다.

더 수정할 부분도 있을듯?

@SpringBootTest
class CrawlingServiceTest {

    @Autowired
    private CrawlingService crawlingService;

    @DisplayName("픽시브 이미지를 크롤링하면 60장의 이미지 url을 긁어온다.")
    @Test
    void CrawlingServiceTest() throws InterruptedException {
        // given
        List<String> result = crawlingService.getImages();
        // expected
        assertThat(result.size()).isEqualTo(60);
    }

}

 


다음 목표

다음 목표는 수동 스크롤할 필요 없이 자동으로 스크롤 하여 이미지를 긁어오는 기능을 구현하는것이다.

문제 발견

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

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

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

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

 


문제 해결

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

  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