본문 바로가기
Back-end/JAVA

[크롤링] Selenium을 이용한 JAVA 크롤러 (1) - HTML 파싱

by 허도치 2020. 2. 27.

2020/02/25 - [Back-end/JAVA] - [크롤링] Jsoup을 이용한 JAVA 크롤러 (1) - HTML 파싱

2020/02/25 - [Back-end/JAVA] - [크롤링] Jsoup을 이용한 JAVA 크롤러 (2) - 파일 다운로드

 

0. 서론

 이전 포스트에서 Jsoup을 이용한 크롤러를 만들어보았다. Jsoup도 간편하게 사용할 수 있어서 좋지만, 동적 웹페이지를 크롤링하는데 적합하지 않다. 그리고, XPath를 지원하지않기 때문에 별도의 라이브러리를 필요로 한다. 이 두 개의 단점을 커버할 수 있는 것이 바로 Selenium이다.

 Selenium은 웹 어플리케이션 테스트를 자동화 할 때 사용하는 툴이다. WebDriver를 통해 웹 브라우저가 실행되며 미리 작성된 스크립트를 통해 사용자가 직접 조작하는 듯한 모습을 눈으로 직접 확인할 수 있다. 또한, 현재 브라우저에 출력된 페이지의 소스를 파싱 할 수 있다.

 Jsoup과 Selenium을 비교해보면 이렇다.

 Jsoup은 백그라운드에서 HTTP Request/Response가 이루어지며, Request를 던졌을 때 웹 서버에서 응답한 결과를 받아온다. 따라서, 서버 사이드 랜더링(SSR, Server-Side Rendering)을 사용하는 웹 사이트는 서버에서 랜더링을 한 후 화면을 그리기 때문에 크롤링이 가능하지만, 클라이언트 사이드 랜더링(CSR, Client-Side Redering)을 사용하는 웹 사이트는 최소한의 페이지만 서버에서 랜더링하고 클라이언트(브라우저)에서 나머지 화면을 랜더링하기 때문에 HTTP Request로는 실제 브라우저에서 보여지는 화면을 스크랩 할 수 없다. 하지만, Selenium은 현재 브라우저에 출력된 페이지의 소스파싱 할 수 있다는 특성을 이용하여 CSR을 사용하는 웹 사이트도 크롤링 할 수 있다. 또한, XPath를 지원하고 Javascript 명령어를 실행할 수 있기 때문에 Jsoup보다 많은 데이터를 크롤링 할 수 있다.

 Selenium을 이용한 크롤러가 Jsoup보다 마냥 좋은 것은 아니다. Jsoup은 HTTP Request를 통해 웹서버에 직접 요청하기 때문에 빠른 응답을 받을 수 있다. 하지만, Selenium은 브라우저가 랜더링 된 후 페이지를 파싱하기 때문에 수집 속도가 느리다. 따라서, 동적 웹페이지가 아니라면 Jsoup을 이용하는 것이 좋다.

 그럼 이제 예제를 통해 Selenium을 이용한 크롤러를 만들어 보자.
 

 

 

 

1. 프로젝트 준비
1-1. ChromeWebDriver 다운로드

1) ChromeWebDriver 70~73 버전 다운로드
   : 72.0.3626.7 버전 [ 다운로드 ]
   : driver는 아무데나 저장해도 상관없으나, Selenium을 설정 할 때 경로를 지정해주어야 함.

 

1-2. PATH 환경변수 설정

1) WebDriver 저장 경로
   : [ Windows ] - C:\drivers
   : [ mac/linux ] - $HOME/bin

2) PATH 환경변수 설정
   : [ Windows ] - $ SETX -m PATH "%PATH%;C:\drivers;"
   : [ Mac/Linux ] - $ export PATH=$PATH:$HOME/bin

3) 더 자세한 내용은 공식 문서를 참조.

 

 

1-3. selenium-java 라이브러리 다운로드

1) Maven Project를 사용하는 경우, dependency 추가
    <dependency>
      <groupId>org.seleniumhq.selenium</groupId>
      <artifactId>selenium-java</artifactId>
      <version>3.141.59</version>
    </dependency>
 

 

 

 

 

2. Selenium 테스트
2-1. 스크립트 작성
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// App.java
package selenium;
 
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
 
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
 
public class App {
    
    public static void main(String[] args) {
        
        // 현재 package의 workspace 경로, Windows는 [ chromedriver.exe ]
        Path path = Paths.get(System.getProperty("user.dir"), "src/main/resources/chromedriver");  // 현재 package의
        
        // WebDriver 경로 설정
        System.setProperty("webdriver.chrome.driver", path.toString());
        
        // WebDriver 옵션 설정
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--start-maximized");            // 전체화면으로 실행
        options.addArguments("--disable-popup-blocking");    // 팝업 무시
        options.addArguments("--disable-default-apps");     // 기본앱 사용안함
        
        // WebDriver 객체 생성
        ChromeDriver driver = new ChromeDriver( options );
        
        // 빈 탭 생성
        driver.executeScript("window.open('about:blank','_blank');");
        
        // 탭 목록 가져오기
        List<String> tabs = new ArrayList<String>(driver.getWindowHandles());
        
        
        
        // 첫번째 탭으로 전환
        driver.switchTo().window(tabs.get(0));
        
        // 웹페이지 요청
        driver.get("https://heodolf.tistory.com/101");
        
        // 웹페이지에서 글제목 가져오기
        WebElement page1_title = driver.findElementByXPath("//*[@id=\"content\"]/div[1]/div[1]/div/h1");
        if( page1_title != null  ) {
            System.out.println( page1_title.getText() );            
        }
        // 웹페이지 소스 출력
        //System.out.println( driver.getPageSource() );
        
        // 탭 종료
        driver.close();
        
        
        
        // 두번째 탭으로 전환
        driver.switchTo().window(tabs.get(1));
        
        // 웹페이지 요청
        driver.get("https://heodolf.tistory.com/102");
        
        // 웹페이지에서 글제목 가져오기
        WebElement page2_title = driver.findElementByXPath("//*[@id=\"content\"]/div[1]/div[1]/div/h1");
        if( page1_title != null  ) {
            System.out.println( page2_title.getText() );            
        }
        
        // 웹페이지 소스 출력
        //System.out.println( driver.getPageSource() );
        
        // 탭 종료
        driver.close();
        
        
        
        // 5초 후에 WebDriver 종료
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // WebDriver 종료
            driver.quit();
        }
    }
}
cs

1) [ 18~21 ln ] - WebDriver 설치 경로 설정
   : Java Application을 시작할 때, Arguments로 -Dwebdriver.chrome.driver=<경로> 를 적용해도 됨.

2) [ 24~27 ln ] - WebDriver 옵션 설정
   : 위에서 설정한 옵션 외에도 Chrome Browser에 적용할 수 있는 옵션들은 매우 많음.
   : 추가 옵션들은 웹사이트를 참조.

3) [ 33 ln ] - Javascript 명령 실행
   : window.open( URL, TARGET )
   : URL에 'about:blank'를 입력하여 빈 탭을 생성하였지만, 주소를 입력하여 바로 웹 페이지를 요청할 수 있음.

4) [ 36~41 ln ] - 새로운 탭으로 전환
   : 크롤링 보다는 웹 어플리케이션을 테스트할 때 주로 사용됨.

5) [ 46 ln ] - 웹 페이지 요청

6) [ 47~50 ln ] - XPath를 이용하여 Elements 탐색
   : XPath는 XML을 이용한 탐색 방식인데, CSS Selector보다 더 많은 기능을 제공함.

7) [ 52 ln ] - 현재 출력된 페이지의 페이지 소스를 출력

8) [ 55 ln ] - 현재 탭을 종료

9) [ 86 ln ] - WebDriver를 종료

 

2-2. 실행

1) IDE를 이용하는 경우
   : Java Application 실행

 

2-3. 실행 결과

[ 실행 로그 ]
[ WebDriver 실행 화면 ]

1) 어플리케이션을 실행하면 WebDriver에 의해서 Chrome 브라우저가 실행됨.
   : 상단에 'Chrome이 자동화된 테스트 소프트웨어에 의해 제어되고 있습니다.'라는 문구가 출력됨.

2) Chrome 브라우저가 실행되면 2개의 탭이 생김.

3) 순차적으로 입력한 웹 페이지를 요청하고, 글제목을 출력한 후 탭이 종료됨.

4) 마지막에는 5초 후에 WebDriver가 완전히 종료됨.

 

2-4. driver.close()와 driver.quit()의 차이

1) driver.close()
   : 탭을 종료함.
   : close를 통해 모든 탭이 종료되었을 때, WebDriver도 닫히게 되는데 표면적으로는 종료된 것처럼 보이지만 Process가 살아있어서 자원의 낭비를 유발함.
   : 직접 프로세스를 종료해야함.

2) driver.quit()
   : WebDriver를 종료함.
   : close로 모든 탭을 종료시켰을 때와는 다르게 Process 자체를 종료함.

3) Process 종료 명령어
   : [ Windows ] - TASKKILL /F /IM chromedriver.exe
   : [ Mac/Linux ] - for pid in $(ps -ef | grep -i chromedriver | grep -v grep | awk '{print $2}' ); do kill $pid; done;

 

 

 

마치며

- 예제를 만들면서 다른 포스트들을 크롤링하였는데, Jsoup을 이용할 때는 조회수에 영향을 미치지 않았다. 그런데, Selenium을 이용하니까 조회수가 증가하였다. 이렇게 조회수를 조작하는 사람도 있지 않을까라는 생각이 들엇다.
- Elements를 탐색할 때는 역시 CSS Selector보단 XPath가 편하다.

댓글