Web Scraping trong Ruby với Watir
Watir là một thư viện mã nguồn mở sử dụng cho việc automating test. Watir tương tác với trình duyệt giống như cách con người làm: nhấp vào liên kết, điền vào các biểu mẫu và xác nhận văn bản. Nhưng không chỉ vậy, tôi sẽ xây dựng một web scraper mô phỏng việc vào một trang để đăng nhập, comment, tải ...
Watir là một thư viện mã nguồn mở sử dụng cho việc automating test. Watir tương tác với trình duyệt giống như cách con người làm: nhấp vào liên kết, điền vào các biểu mẫu và xác nhận văn bản. Nhưng không chỉ vậy, tôi sẽ xây dựng một web scraper mô phỏng việc vào một trang để đăng nhập, comment, tải dữ liệu và nhiều thứ khác.
Chức năng chính của Watir là nó sử dụng Selenium web-driver, có nghĩa là chúng ta có thể tạo một trang web động với JavaScript. Một gem tương tự là Mechanize - phù hợp cho các trang tĩnh và không sử dụng nhiều JavaScript hoặc AJAX.
Modes
Có thể chạy Watir trong trình duyệt bình thường (Chrome/Firefox) hoặc headless.
Chế độ headless cho phép phân tích một trang không có giao diện - trong hầu hết các hệ thống UNIX, Watir yêu cầu cài đặt sẵn Xvfb (trên Ubuntu). Watir sử dụng PhantomJS để mô phỏng trình duyệt web và chạy một trang trong trình mô phỏng. Nếu muốn phân tích trang bằng Chrome, cần cài đặt chrome-driver. Ngoài ra, Watir còn cho phép chạy một trang như iPhone, iPad hoặc các thiết bị di động khác.
Tôi sẽ xây dựng một gem đơn giản, cho phép đăng ký, đăng nhập, mời bạn bè hoặc like trang trên Facebook.
Source code
Nào cùng bắt đầu.
def initialize(email, password) @email = email @password = password end
Đơn giản là chỉ định email và mật khẩu.
def browser @_browser ||= Watir::Browser.new(:chrome) end
Chỉ định trình duyệt chạy là Chrome. Nếu bỏ qua phantomjs ở đây, nó sẽ chạy chế độ headless.
def login return true if @logged_in browser.goto('https://www.facebook.com/') form = browser.form(id: 'login_form') return false unless form.exist? form.text_field(name: 'email').set(email) form.text_field(name: 'pass').set(password) form.input(value: 'Log In').click sleep(2) @logged_in = main_page? end
Phương thức đăng nhập vào Facebook với các thông tin cần thiết.
- Sử dụng goto để redirect từ trang hiện tại sang trang chủ Facebook.
- form - tìm kiếm form đăng nhập của Facebook với id: login_form.
Chú ý ở đây, nếu tìm một phần tử không tồn tại thì script trên sẽ đợi (mặc định 30s) sau đó sẽ trả về false. Nên tốt nhất là bạn kiểm tra xem phần tử đó có thực sự có trên trang web đích không.
- text_field tìm field input và dùng phương thức set truyền params vào field.
- dễ thấy phương thức click dùng để nhấp chuột vào một phần tử.
Tại sao phải chạy phương thức sleep trong 2s? - là để chờ cho tất cả asset và javascript được tải xong.
def main_page? browser.element(id: 'userNavigationLabel').exist? end
Phương thức main_page? kiểm tra nếu thanh điều hướng tồn tại nghĩa là đã đăng nhập thành công.
def registration_params_valid?(params) return false unless params.keys.uniq.sort == REGISTRATION_INPUTS.uniq.sort return false if params.values.map(&:blank?).include?(true) return false if EMAIL_REGEX.match(params[:email]).nil? true end
registration_params_valid? kiểm tra tất cả các trường đăng ký đều đã được điền và hợp lệ.
def create_account(**args) raise unless registration_params_valid?(args) browser.goto('https://www.facebook.com/') form = browser.form(id: 'reg') form.text_field(name: 'firstname').set(args[:first_name]) form.text_field(name: 'lastname').set(args[:last_name]) form.text_field(name: 'reg_email__').set(email) form.text_field(name: 'reg_email_confirmation__').set(email) form.text_field(name: 'reg_passwd__').set(password) form.select_list(name: 'birthday_day').select(args[:day]) form.select_list(name: 'birthday_month').select(args[:month]) form.select_list(name: 'birthday_year').select(args[:year]) form.radio(name: 'sex', value: sex(args[:sex])).set form.button(name: 'websubmit').click end
Phương thức create_account dùng để đăng ký trên Facebook. Check registration_params_valid? trước khi đến main page Facebook và tiến hành đăng ký.
def sex(value) value.downcase.strip == 'male' ? '2' : '1' end
Phương thức này nhận vào tham số và trả ra giá trị hợp lệ.
def search(query) login unless logged_in form = browser.form(action: '/search/web/direct_search.php') form.inputs.last.to_subtype.clear sleep(0.5) form.inputs.last.to_subtype.set(query) form.button(type: 'submit').click end
Phương thức này tìm đến field input tìm kiếm của trang sau khi kiểm tra đã được đăng nhập. Nếu không, thực hiện đăng nhập trước. Đôi khi watir đã click quá nhanh và không phải các phần tử đều đã được nhập, sử dụng sleep để chờ.
def perform(query, options = {}) login unless logged_in search(query) browser.link(href: "/search/#{options[:name]}/?q=#{query}&ref=top_filter").click button = browser.button(class_name: options[:class_name]) button.click if button.exist? end
Phương thức này dùng để mời một người bạn, like page, vv... Cần truyền một truy vấn, class của nút cần click và tên tab. Nhớ rằng, nút đầu tiên tìm thấy sẽ được click.
def like_page(name) perform(name, name: 'pages', class_name: 'PageLikeButton') end
def invite_friend(name) perform(name, name: 'people', class_name: 'FriendRequestAdd') end
Để sử dụng phương thức perform, chỉ cần truyền truy vấn và click vào nút bên phải rồi chuyển sang đúng tab.
Testing
Các phương thức cơ bản đã đầy đủ. Bạn có thể download tại đây.
Chỉ cần clone, chạy bundle install và thưởng thức:
$ bundle console $ scraper = NopioScraper::Facebook.new(‘your_email’',’your password’) $ scraper.like_page('nopio')
Tất nhiên, ví dụ này thực sự đơn giản. Chẳng may trình duyệt bật popup hay cảnh báo thì bạn phải sửa lại code của tôi