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. 실행 결과
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가 편하다.
'Back-end > JAVA' 카테고리의 다른 글
[JAVA] 파일 분할 - 용량 단위, 위에서 아래로 ↓ (0) | 2020.10.22 |
---|---|
[크롤링] Selenium을 이용한 JAVA 크롤러 (2) - Jsoup과 비교 (With. Twitter) (0) | 2020.02.28 |
[크롤링] Jsoup을 이용한 JAVA 크롤러 (2) - 파일 다운로드 (0) | 2020.02.25 |
[크롤링] Jsoup을 이용한 JAVA 크롤러 (1) - HTML 파싱 (1) | 2020.02.25 |
[SMTP] JAVA로 메일 발송 하기. (With. 첨부파일 ) (3) | 2020.02.20 |
댓글