본문 바로가기

토이프로젝트/리뷰어(영화 리뷰 사이트)

영화 정보 크롤링 Api 구현

이전에 크롤링 하는 법을 정리해놨는데, 이걸 통해서 크롤링 할 수 있는 api를 구현했다.

영화 정보 크롤링

@Service
@RequiredArgsConstructor
public class MovieCrawlingService {

  private static final String weekRankingUrl = "http://www.cine21.com/rank/boxoffice/domestic";
  private static String WEB_DRIVER_ID = "webdriver.chrome.driver";
  private static String WEB_DRIVER_PATH = "./chromedriver.exe";

  private final MovieRepository movieRepository;
  private final ActorRepository actorRepository;

  private WebDriver startDrive(String url) {
    System.setProperty(WEB_DRIVER_ID, WEB_DRIVER_PATH);
    ChromeOptions options = new ChromeOptions().addArguments(
        "--disable-popup-blocking", // 팝업 안띄움
        "headless", // 브라우저 안띄움
        "--disable-gpu", // gpu 비활성화
        "--blink-settings=imagesEnabled=false", // 이미지 다운 안받음
        "--remote-allow-origins=*"); //없으면 접근 안됨
    WebDriver driver = new ChromeDriver(options);
    driver.get(url);
    return driver;
  }

  @Transactional
  public void getWeekRankingList() {
    List<MovieWeekRankingDto> movieDtoList = getWeekRankingDtoList();
    for (MovieWeekRankingDto dto : movieDtoList) {
      Movie movie = movieRepository.save(MovieCrawlingConverter.toMovie(dto));
      System.out.println(dto.getActor());
      actorRepository.saveAll(MovieCrawlingConverter.toActors(movie, dto.getActor()));
    }
  }

  public List<MovieWeekRankingDto> getWeekRankingDtoList() {
    WebDriver driver = startDrive(weekRankingUrl);
    List<MovieWeekRankingDto> movieDtoList = new ArrayList<>();
    try {
      List<WebElement> elements = driver.findElements(By.cssSelector("li.boxoffice_li > a"));
      List<String> detailList = elements.stream().map(item -> item.getAttribute("href")).toList();

      for (int i = 0; i < Math.min(10, detailList.size()); i++) { //갯수 지정
        driver.navigate().to(detailList.get(i));
        WebElement element = driver.findElement(By.cssSelector("div.mov_info"));
        WebElement imgAttr = driver.findElement(By.cssSelector("div.mov_poster > img"));
        movieDtoList.add(
            MovieWeekRankingDto.builder()
                .movieCode(detailList.get(i).split("=")[1])
                .imgLink(imgAttr.getAttribute("src") != null ? imgAttr.getAttribute("src") : "")
                .summary(findElementByCssSelector(driver, "div.story_area > div"))
                .title(findElementByCssSelector(element, "p.tit"))
                .nation(findElementByCssSelector(element, "p:nth-child(3) > span:nth-child(2)"))
                .genre(findElementByCssSelector(element, "p:nth-child(4) > span:nth-child(1)"))
                .director(findElementByCssSelector(element, "p:nth-child(6) > a"))
                .runningTime(
                    findElementByCssSelector(element, "p:nth-child(4) > span:nth-child(2)"))
                .actor(findElementByCssSelector(element, "p:nth-child(7)"))
                .build());
      }
      driver.quit();
    } catch (Exception e) {
      driver.quit();
    }
    return movieDtoList;
  }

  private String findElementByCssSelector(WebDriver element, String cssSelector) {
    try {
      WebElement attribute = element.findElement(By.cssSelector(cssSelector));
      return attribute.getText() != null ? attribute.getText() : "";
    } catch (Exception e) {
      return "";
    }
  }

  private String findElementByCssSelector(WebElement element, String cssSelector) {
    try {
      WebElement attribute = element.findElement(By.cssSelector(cssSelector));
      return attribute.getText() != null ? attribute.getText() : "";
    } catch (Exception e) {
      return "";
    }
  }

드라이브와 관련된 생성부분을 분리했고, 중간에 어떤 오류로 인해 강제종료가 되는 상황을 위해서 try-catch문으로 에러를 방지했다.
그리고 원하는 정보가 없거나 크롤링할 웹사이트의 구조가 바뀌었을 수도 있으니 문자열이 null이 되는 상황도 없애주었다.

일단 동작은 잘 되는데 무지성으로 크롤링할 경우 영화의 갯수가 너무 많아서 렉이 너무 심해지는 현상이 있었다.
그래서 search 부분을 추가해 갯수에 제한을 두는 기능을 추가해야 될 것 같다.