Web crawler với Selenium Webdriver và PhantomJS - Phần 1

I, Mở đầu:

Trong quá trình làm dự án về Crawler Web bằng Ruby on rails của mình, thì hầu hết mình thường sử dụng gem Mechanize để crawl dữ liệu. Gem Mechanize là một thư viện rất mạnh với nhiều công cụ như navigate page, click button, link, fill form and submit...Mechanize request dữ liệu dưới dạng HTML nên những xử lý về JS bên trong thì thường chúng ta phải làm trình tự các bước giống như trong hàm JS. Đối với những hàm JS đơn giản, không quá dài, chúng ta có thể làm nó bình thường mà không gặp chút khó khăn gì, nhưng nếu gặp phải những hàm JS khởi tạo đến hàng nghìn dòng lệnh thì dùng Mechanize để xử lý nó là việc làm không thể.

Để xử lý những trang rắc rối như vậy thì hôm nay mình xin giới thiệu một công cụ rất hay là Selenium Webdriver. Selenium Webdriver ngoài việc được dùng nhiều trong công việc QA thì nó còn được ứng dụng rất hay trong Crawler web.

II, Giới thiệu:

Selenium thực ra là một công cụ giúp tự động hóa quá trình của một người dùng bình thường trên browser. Từ việc access vào trang chủ, next page, submit form, click button, link... đều được tiến hành một cách tự động. Sự khác biệt giữa Mechanize và Selenium Webdriver là với Mechanize dữ liệu được request ở dạng HMTL, nên các hàm JS chúng ta phải tự thực hiện đầy đủ nếu cần, còn Selenium thì giống như người dùng, nó request toàn bộ HMTL, image hay JS.. Do đó, sử dụng Selenium có thể khiến tốc độ xử lý chậm hơn hay tốn bộ nhớ hơn. Để sử dụng chúng ta phải add gem vào project:

    gem selenium-webdriver

III, Selenium Webdriver

Chúng ta hãy cùng tìm hiểu một số thứ cơ bản:

1, Driver

Driver có thể khởi tạo trên nhiều trình duyệt khác nhau: firefox, chrome, safari, opera, phantomjs...

    require "selenium-webdriver"

    driver = Selenium::WebDriver.for :firefox

    driver = Selenium::WebDriver.for :chrome

    driver = Selenium::WebDriver.for :opera

    driver = Selenium::WebDriver.for :safari

Từ giờ thì driver luôn trả về trạng thái hiện tại của bạn. Để access vào một trang bất kì, có 2 cách:

    driver.navigate.to "http://google.com"

    driver.get "http://viblo.asia"

Chúng ta có thể đóng browser khi công việc hoàn thành bằng lệnh:

    driver.quit

Một điểm thú vị RemoteWebDriver cho phép bạn có thể khởi tạo và điều khiển trình duyệt trên một máy tính khác bằng cách:

  • Download file Jar và launch trên server: java -jar selenium-server-standalone.jar

  • Sau đó, kết nối đến nó từ Ruby

    driver = Selenium::WebDriver.for(:remote)
  • Mặc định, kết nối sẽ đến server: http://localhost:4444, với trình duyệt firefox. Để kết nối đến một máy khác, sử dụng option url
    driver = Selenium::WebDriver.for(:remote, :url => "http://myserver:4444/wd/hub")
  • Để thực thi trên một trình duyệt khác, sử dụng option desired_capabilities
    driver = Selenium::WebDriver.for(:remote, :desired_capabilities => :chrome)
  • Hay nếu muốn set một proxy cho browser remote:
    caps = Selenium::WebDriver::Remote::Capabilities.firefox(:proxy => Selenium::WebDriver::Proxy.new(:http => "myproxyaddress:8080"))
    driver = Selenium::WebDriver.for(:remote, :desired_capabilities => caps)

2, Một số công thức thông dụng:

  • Thực thi một lệnh script:
    puts driver.execute_script("name of script")
  • Finder: giúp tìm các phần tử trong page dựa vào: id, name, link_text, partial link text, Xpath, css, class... đại khái là khá đa dạng:
    driver.find_element(id: "submit_btn")

    driver.find_element(name: "comment")

    driver.find_element(xpath: "//*[@id='div2']/input[@type='checkbox']")

    driver.find_element(class: "class_name")

    driver.find_element(css: "#div2 > input[type='checkbox']")

Kết hợp 2 lệnh lại ta có thể tóm lấy 1 phần tử để tìm 1 phần tử khác:

    driver.find_element(id: "div2").find_element(name: "same").click

Tìm nhiều phần tử, kết quả trả về là 1 mảng:

    checkbox_elems = driver.find_elements(xpath: "//div[@id='container']//input\
    [@type='checkbox']")
  • Chờ cho một element được show up:
    wait = Selenium::WebDriver::Wait.new(:timeout => 10) # seconds
    wait.until { driver.find_element(:id => "foo") }
  • Switch đến một frame:
    driver.switch_to.frame "some-frame" # name or id
    driver.switch_to.frame driver.find_element(:id, 'some-frame') # frame element
  • Switch back lại document chính:
    driver.switch_to.default_content
  • Custom lại cửa sổ trình duyệt:
    driver.manage.window.move_to(300, 400)
    driver.manage.window.resize_to(500, 800)
    driver.manage.window.maximize
  • Trả lại url của driver:
    driver.current_url

3, Một số công thức làm việc với element

  • Get một attribute của element:
    class_name = element.attribute("class")
  • Check một element có visible trên page hay không:
    element.displayed?
  • Click vào một element(cũng giống như khi người dùng click vào):
    element.click
  • Get location của element:
    element.location
  • Scroll phần từ vào trong view, sau đó trả lại vị trí cũ:
    element.location_once_scrolled_into_view
  • Get kích thước của element như chiều rộng, chiều cao:
    element.size
  • Get text của element:
    element.text

4, Timeouts

  • Implicit waits: Giúp set thời gian đợi ngầm, khi gọi method find_element, nó sẽ đợi một khoảng thời gian nào đó trước khi raise ra lỗi: NoSuchElementError
    driver = Selenium::WebDriver.for :firefox
    driver.manage.timeouts.implicit_wait = 3 # seconds
  • Explicit waits: Sử dụng lớp Wait trong một số câu điều kiện:
    wait = Selenium::WebDriver::Wait.new(:timeout => 3)
    wait.until { driver.find_element(:id => "cheese").displayed? }
  • Internal timeouts: Webdriver sử dụng HTTP để communicate với rất nhiều driver, mặc định Net::HTTP sử dụng time out mặc định là 60 giây, Nếu bạn gọi các method như: driver.get, driver.click mà nếu quá 60s thì sẽ xảy ra lỗi Timeout::Error từ Net::HTTP. Chúng ta có thể config lại bằng lệnh:
      client = Selenium::WebDriver::Remote::Http::Default.new
      client.timeout = 120 # seconds
      driver = Selenium::WebDriver.for(:remote, :http_client => client)

5, JavaScript dialogs

Bạn có thể sử dụng Webdriver để handle alert(), prompt(), confirm() dialogs. Với chú ý rằng handle alert chỉ available trên firefox và IE.

    require "selenium-webdriver"

    driver = Selenium::WebDriver.for :firefox
    driver.navigate.to "http://mysite.com/page_with_alert.html"

    driver.find_element(:name, 'element_with_alert_javascript').click
    a = driver.switch_to.alert
    if a.text == 'A value you are looking for'
      a.dismiss
    else
      a.accept
    end

IV, Lời kết

Trên đây là giới thiệu và một số câu lệnh thông dụng của Selenium Webdriver trên ngôn ngữ Ruby. Việc chạy ứng dụng trên các trình duyệt thông thường sẽ bộc lộ một số hạn chế đã nêu ở trên. Vì vậy chúng ta sẽ tiếp tục bài viết này với việc sử dụng và custom PhantomJS. Nó sẽ được trình bày ở bài viết sau kèm với ví dụ cụ thể. Thanks for read

Nguồn tài liệu:

http://www.rubydoc.info/gems/selenium-webdriver/0.0.28/Selenium/WebDriver/Driver

https://github.com/SeleniumHQ/selenium/wiki/Ruby-Bindings

Book: selenium-recipes-in-ruby-sample of Zhimin Zhan

http://www.seleniumhq.org/docs/03_webdriver.jsp