2018년 8월 5일 일요일

단일 페이지 어플리케이션의 웹 크롤링


단일 페이지 어플리케이션(SPA : Single Page Application)은 웹을 구성하는 정적인 리소스를 한번에 다운로드하고 새로운 페이지 구성에는 필요한 데이터만 서버로부터 받거나 클라이언트에서 만들어 사용합니다. ,  동적으로 웹 페이지를 구성합니다.  동적으로 웹 페이지가 만들어진다는 점에서 웹 크롤링에 어려움이 있습니다.  SPA 구성에는 주로 backbone, angular,  React와 같은 javascript framework를 이용하는데  backbonejs를 이용한 예제가  http://backbonejs.org/examples/todos/index.html 입니다.  



“book4”“book5”는 사용자의 키 입력과 javascript가 실행되면서 만들어진 요소입니다.  웹브라우저에서 제공하는 소스 보기기능을 이용해도  “book4” “book5”가 보이지 않아 당황스럽습니다.



http://backbonejs.org/examples/todos/index.html를 두가지 방법으로 웹 크롤링을 합니다. 하나는 selenium과 파이썬을 이용하는 방법이고  다른 하나는 casperjs를 이용한  방법입니다.
selenium를 사용하려면 웹 브라우저에 맞은 드라이버를 구해야 합니다.  여기서는 firefox를 사용할 것이라 https://github.com/mozilla/geckodriver/releases 에서 geckodrive 이름으로 된 드라이버를 구합니다. 압축을 푼 geckodriver.exe는 파이썬 실행 프로그램과 같은 디렉토리에 둡니다.  그리고 아래 파이썬 프로그램을 실행합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

driver = webdriver.Firefox()   # keckodriver in the same directory with this script
driver.implicitly_wait(3)
driver.get('http://backbonejs.org/examples/todos/index.html')


driver.find_element_by_id("new-todo").send_keys('book4')
driver.find_element_by_id("new-todo").send_keys(Keys.ENTER)

driver.find_element_by_id("new-todo").send_keys('book5')
driver.find_element_by_id("new-todo").send_keys(Keys.ENTER)

print(driver.page_source)

print("** body **")
print(driver.find_element_by_tag_name("body").text)

print("** li **")
elements = driver.find_elements_by_tag_name("li")
for e in elements:
    print(e.text)

라인 7까지는 웹 페이지 접속을 위한 기본 내용입니다. 라인10에서 14까지는 “book4”“book5”를 등록하는 과정인데  “ENTER”도 따로 보내야 함에 주의합니다.  라인 16 “driver.page_source”를 출력하면 웹 페이지의 html 소스를 볼 수 있는데  아래와 같이 “book4”“book5” 내용이 들어 있습니다. 이것은  웹 브라우저에서 볼 수 없었던 내용이고 이런 방식으로 동적으로 생성된 웹 페이지를 크롤링할 수 있습니다.   라인 19 에서는  <body> 태그에 담긴 텍스트, 라인 22이하에서는 <li> 태그에 담긴 텍스트를 정확하게 가져 옵니다.



selenium는 웹 브라우저를 구동하고 프로그램으로 조작하는 반면에 caspejs는 웹 브라우저 없이 작동합니다.  대신 casperjsphantomjs라는 프로그램 위에서 작동해야 합니다.  윈도우용으로   http://phantomjs.org/download.html 에서 MS 윈도우용 phantomjs를 먼저 구합니다.  압축을 풀면 example를 포함한 여러 파일이 있지만  bin 디렉토리의 phantomjs.exe 만 있으면 됩니다.  원도우 환경 변수 PATHphantomjs.exe가 있는 디렉토리를 지정하도록 합니다.  



윈도우용 casperjs  http://casperjs.org/  바로 첫 화면에서 다운로드 가능하고 모든 파일의 압축을 풀고 환경 변수 PATHcasterjs.exe가 있는 폴더 이름을 포함하도록 합니다.   도스창에서 “casper”만 실행해서 casperjsphanatomjs 버전이 나오면 설치는 성공입니다.



아래는http://backbonejs.org/examples/todos/index.html를 읽기 위한 casperjs 프로그램입니다. javascript인데 파일 이름은 todo3.js라면 도스창에서 “casterjs todo3.js”로 실행합니다.


 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
var casper = require('casper').create();

var bd = [];
var links = [];

function getBody () {
 var b = __utils__.findAll('body');
    return b;
}

function getTodos () {
    var todos = document.querySelectorAll('#todo-list li');
    return todos;
}

casper.start("http://backbonejs.org/examples/todos/index.html", function() {
 links = this.evaluate(getTodos);
    this.echo('num: ' + links.length);
});

// add new todo
casper.then(function() {
    this.sendKeys('#new-todo', 'book1');
    this.sendKeys('#new-todo', casper.page.event.key.Enter);
});

// add new todo
casper.then(function() {
    this.sendKeys('#new-todo', 'book2');
    this.sendKeys('#new-todo', casper.page.event.key.Enter);
});

casper.then(function() {
 bd = this.evaluate(getBody);
 this.echo(bd[0].outerHTML);
    links = this.evaluate(getTodos);
    this.echo('num: ' + links.length);
 this.echo("0: " + links[0].outerHTML);
 this.echo("1: " + links[1].outerHTML);
});

casper.run();

첫번째 라인은 casperjs를 시작하기 위한 casper 변수 선언입니다.  나중에 다시 언급하겠지만 casperjstest 모드에서 실행하려면 이 줄을 없애야 합니다.  라인6이하와 라인 11이하는  각각 <body><todo-list><li> 태그를 찾기 위한 함수 선언입니다.  라인 16에서  URL를 인수로 하는 casper.start()로 시작합니다. selenium와 달리 명시적으로 wait를 주지 않아도 페이지가 올라올 때까지 기다립니다.  casper API 실행 순서는 start() -> then() -> ... ->  run() 순입니다.  기본적으로 비동기로 실행되는 then이 순서를 정하고 run()은 반드시 존재해야 합니다.   라인 17에서  getTodos()를 호출해서  <todo-list><li> 태그를 가진 요소가 몇개인지 알아 봅니다.  라인 22이하와 라인 28이하를 통해 요소 두개를 추가합니다. 라인 34에서 <body> 태그에 요소를 찾아 HTML 형식으로 출력합니다. 라인 36이하는 다시 <todo-list><li> 태그를 가진 요소를 찾습니다.



여기에 casperjs의 버그가 있습니다. 첫번째 요소를 찾아 출력하는데에는 문제가 없는데 두번째 요소 경우  아예 에러도 없이 아무런 내용이 나오지 않습니다. 아무런 에러가 없다는 것이 더 문제인데 이 에러를 보려면 casperjstest 모드에서 실행해야 합니다.  test 모드는 자동적으로 변수 선언이 있기 때문에 첫번째 라인에서 선언문과 겹칩니다.  



그래서 첫번째 라인의 casper 변수 선언은 comment처리하고  “casperjs test todo3.js” 실행해야 비로소  links[1]null임을 알 수 있습니다.



seleniumjava와 파이썬과 같이 여러 언어도 지원하고 파싱을 위한 beautifulsoup까지 사용할 수 있는 반면에 casperjsjavascript로만 가능하고 따라서 웹 크롤링에 도움이 되는 지원 도구도 거의 없습니다. selenium와 파이썬으로 크롤링하다가 한계나 극복할 수 없는 에러만 생기지 않으면 계속 selenium를 사용할 것 같습니다.

댓글 없음:

댓글 쓰기