개요

자바를 이용한 뉴스 크롤러 만들기.  다음뉴스와 네이버뉴스에 대해서 기사 본문 및 댓글을 가져오는 기능을 만든다. 여기서 데이터를 가져오는 것들은 그렇게 어렵지 않았지만 데이터를 가져오는 과정에서 나타난 문제점들을 위주로 작성하려고 한다. 그 이전에 순차적으로 어떻게 접근했는지에 대해서 보자.


크롤링 혹은 스크래핑이라 불리우는 이 표현은 혼용해서 쓰는 것으로 알고 있다. 웹 상에 떠다니는 데이터에 대해서 읽어들이는 행위를 의미한다. 이하 본문에서는 크롤링(Crawling) 이라는 표현을 사용하고자 한다.


우선적으로 크롤링을 하기위한 라이브러리를 살펴보면 두가지가 있다.

  1. jsoup
    https://jsoup.org/

  2. selenium
    https://www.seleniumhq.org/

이번에 공부하면서 느낀 것은 두가지는 HTML 내부의 DOM 트리에 따른 값을 가지고 오기에 적합하지만 jsoup 같은 경우는 정적인 페이지 즉 페이지의 리로드가 없이 일관된 값만 보여주는 페이지에 적합하고 반면에 selenium은 동적인 페이지, ajax 가 포함된 페이지 혹은 페이지 이동이 빈번하게 일어나는 경우에 적합하다는 것이다.

파서로서의 기능은 jsoup이 단순하게 가지고 있지만 좀 더 확장된 기능과 더불어 페이지에 어떤 액션을 가하는 것에 있어서는 selenium 이 훨씬 낫다고 생각한다. 그래서 셀레니움에 대해서 공부하고 직접 테스트 해보고자 한다. 

설치과정에 대해서는 아래의 내용을 참고하면 좋다.

+) 추가사항
Selenium WebDriver 는 많은 언어를 지원하고 있으며 각각의 언어(Programming Language)마다 자체의 클라이언트 드라이버가 있다.
(1) WebDriver 를 통하여 인스턴스화된 브라우저 객체에 대해 특정 드라이브 (브라우저)를 인스턴스화 시킨다.
(2) (Chrome Browser 기준) ChromeDriver.exe 라는 실행파일을 사용하여 WebDriver 프로토콜을 구현한다. 해당 실행파일은 서버에서 시작하여 모든 테스팅은 서버와 통신하여 테스트를 실행한다.
(3) 드라이버의 실행파일 경로는 두가지로 정해줄 수 있다.
    • 명시적으로 경로를 알려주도록 만든상태
    • 환경변수 Path Variable 에서 크롬 드라이버 경로를 설정해준 상태
      - 해당 경우는 따로 코드상에 명시적으로 알려주지 않아도 상관 없음

Basic
selenium : WebDriver Commands
해당 링크에 접속하면 매우 상세하고 친절하게 설명하고 있다. 그래서 나는 따로 설명하지 않겠다.


Intermediate

selenium : Alerts & windows


이 부분이 오늘 살펴볼 내용이다. 위에 내용들은 튜토리얼 내용만 따라해도 바로 알 수 있는 내용들인 것에 반해서 지금부터는 내가 겪은 문제들이다. 


1) org.openqa.selenium.TimeoutException

2) ElementNotVisibleException

3) NoSuchElementException

4) StaleElementReferenceException


셀레니움으로 웹 애플리케이션에 접근하다보면, 페이지가 사용자에 의해 상호작용으로 로드되고 그 시간간격이 일정하지 않다는 점을 확인할 수 있다. 요소를 식별하는 것이 안될 수도 있을 뿐더러 요소가 없으면 ElementNotVisibleException 예외를 발생한다. 이러한 문제들은 wait를 사용하여 해결할 수 있다. 


Thread.sleep() 을 이용하는 것을 그다지 추천하지 않는다. 왜냐하면 ajax call 로 인해서 응답받을 수 있는 데이터 시간이 불분명하고 해당되는 스레드를 실행 큐에서 다시 대기 큐로 옮겨가면서 유휴시간이 발생하고 결과적으로 메모리와 시간을 낭비하기 때문이다. (오버헤드 발생)


따라서 thread.sleep() 말고 다른 방법을 제시하고자 한다. 


wait를 하는 것에 있어서도 우선순위가 존재한다.

  1. Explicit wait (10 sec)

  2. Implicit wait (20 sec)

우선 명시적으로 선언해놓은 Explicit wait 가 발동되고 해당 시간 내(20초 이내)에 찾고자 하는 요소를 발견하지 못한 경우 Implicit wait 가 발동된다.

Implicit wait 암묵적 대기 )
1
2
3
4
ChromeDriver webDriver = new ChromeDriver();
webDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
 
webDriver.get(URI);
cs


첫번째 인자는 정수형(int) 값, 두번째 인자는 시간 측정을 허용하기 위한 기준. 이렇게 암묵적 대기 시간을 줌으로써, WebDriver가 즉시 가져올 수 없는 요소들을 찾는 경우 특정시간 DOM을 Polling 하도록 기다린다. 결과적으로 브라우저 내 페이지의 요소를 검색할 대기시간을 설정할 수 있다.


Explicit wait ( 명시적 대기 )

명시적 대기는 WebDriver 에게 ElementNotVisibleException 예외를 throw 하기 이전에 특정 조건을 충족하거나 최대 시간을 초과할 때까지 대기하도록 명시한다. 명시적인 대기는 동적으로 로드되는 ajax요소를 기다린다.


명시적인 대기는 Fluent Wait 를 사용하여 조건 확인 빈도를 구성할 수 있다. 


요소를 동적으로 기다리는 가장 좋은 방법

매 초마다 주어진 조건을 검사하고 조건이 충족되는 스크립트에서 다음의 명령을 계속해서 수행하는 방식으로 해결이 가능하다. 만약 해당 시간 내에 찾고자 하는 요소가 존재하지 않는다면 Fluent wait 를 이용해서 해결할 수 있다.


Fluent wait 를 이용해 타임 아웃 및 폴링 간격을 조절할 수 있다.

  • withTimeout() : 전체 시간

  • pollingEvery() : 폴링 시간

    - withTimeout() 의 최대 시간 동안 매 폴링 시간 만큼 웹 페이지의 요소를 검색

1
2
3
FluentWait<WebDriver> fluentWait = new FluentWait<WebDriver>(webDriver);
fluentWait.withTimeout(Duration.ofMinutes(10));        // FluentWait 인스턴스가 조건을 기다리는 최대 시간 (10분 설정)
fluentWait.pollingEvery(Duration.ofSeconds(10));    // CPU가 리소스에 접근하기 위한 폴링 간격 조절 (10초)
cs


그리고 until() 메소드를 수행하기 위한 함수를 함수를 초기화한다.

  • Function<WebDriver, Boolean> commentViewFunction

  • fluentWait.until(commentViewFunction);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// <입력 파라미터, apply() 메소드 반환 값>
Function<WebDriver, Boolean> commentViewFunction = new Function<WebDriver, Boolean>(){
    @Override
    public Boolean apply(WebDriver webDriver) {
        // [ 댓 글 더 보 기 ] 계속 클릭 : 다음은 하나의 화면 내에서 댓글을 볼 수 있도록 관리 되어있음
        WebElement viewMoreComment = webDriver.findElement(By.xpath("//*[@id='alex-area']/div/div/div/div[3]/div[1]/a"));
        
        // 해당 요소 사이즈 확인
        Dimension dimension = viewMoreComment.getSize();
        int width = dimension.getWidth();
        int height = dimension.getWidth();
        
        // 중단 (클릭할 요소가 없기 때문에)
        if(width == 0 && height == 0){
            return true;
        }
        // 계속 시행
        else{
            viewMoreComment.click();
            return false;
        }
    }
};
 
fluentWait.until(commentViewFunction);
cs


until() 메소드가 호출되고 플로우는 아래와 같이 나타낼 수 있다.


[ http://toolsqa.com/selenium-webdriver/advance-webdriver-waits/ ]


자세한 내용은 아래의 글들을 참고하도록 하자.


[ Implicit Wait & Explicit Wait in Selenium ]

[ Advance WebDriver Waits ]


Posted by doubler
,