- vừa được xem lúc

Crawl dữ liệu trong Rails với gem Mechanize

0 0 31

Người đăng: Thơ Trần

Theo Viblo Asia

Mình cần tạo một ứng dụng crawl giá bán các sản phẩm từ một trang web bán hàng nước ngoài (trang A), và tính theo công thức của mình để đổi sang giá bán VNĐ. Đây chính là tiền đề cho ý tưởng dùng Ruby on Rails tạo web crawl dữ liệu của mình. Sau một hồi tìm hiểu thì mình tìm thấy gem Mechanize được mọi người đề xuất khá nhiều vì dễ cài đặt và sử dụng, chúng ta hãy cùng thử xem sao nhé!

1. Giới thiệu gem Mechanize

Thư viện Mechanize được sử dụng để tự động hóa tương tác với các trang web. Nó có cơ chế tự động lưu trữ và gửi cookie, chuyển hướng trang, link, điền và gửi biểu mẫu..

2. Cài đặt

  • Trước tiên, bạn cần cài đặt ruby phiên bản >= 1.9.2 và gem nokogiri
  • Sau đó thêm gem "mechanize" vào Gemfile

Chạy lệnh:

bundle install

Hoặc bạn cũng có thể chạy lệnh command line dưới đây:

sudo gem install mechanize

Sau khi hoàn tất cài đặt, chúng ta có thể sử dụng luôn mà không cần phải config gì thêm.

3. Sử dụng

Quay lại với bài toán của mình:

Input: Mình có một danh sách các mã sản phẩm, các mã này luôn xuất hiện trên url trang detail sản phẩm đó (trang A).

Output: Cái mình cần lấy ra là các thông tin về ảnh, giá cả, tỉ lệ giảm giá, kích thước sản phẩm, tình trạng kho (còn hay hết)...

Đầu tiên, mình cần tạo một ứng dụng Rails:

rails new crawl_product

Sau đó cài gem Mechanize như phía trên mục 2. Tiếp theo, để đơn giản mình sẽ dùng lệnh generate scaffold của rails để tạo model, views, controller cho sản phẩm.

rails generate scaffold Product code:string current_price_rub:integer old_price_rub:integer discount:integer current_price_vnd:integer image_url:string teger stock_status:integer diameter:integer

Tại form mình chỉ cho nhập code, các trường còn lại sẽ được gán giá trị thông qua crawl data từ web bán hàng A.

# /views/products/_form.html.erb <%= form_with(model: product, local: true) do |form| %> <% if product.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(product.errors.count, "error") %> prohibited this product from being saved:</h2> <ul> <% product.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div class="form-group"> <h4><%= form.label :code, for: "exampleInputEmail1", class: "badge badge-dark" %><h4> <%= form.text_field :code, class: "form-control", placeholder: "Enter your code here! Example: 123456" %> </div> <div class="actions"> <%= form.button(type: "submit", class: "btn btn-info") do %> <i class="fa fa-check"></i> Create <% end %> </div>
<% end %>

Tiếp theo, mình cần viết một cái service để crawl data, tại đường dẫn lib/services/crawl.rb

# lib/services/crawl.rb module Services class Crawl def initialize(product) @product = product @agent = Mechanize.new end def get_page @agent.get "https://example.net/catalog/product_#{@product.code}.html" end def perform! get_page # dưới đây là các method mà Mechanize cho phép thực thi để lấy ra dữ liệu dựa trên tên thẻ, tên class, thuộc tính html @product.old_price_rub = @agent.page.search('.product-price.old').map(&:text).map(&:strip).uniq.first.gsub(" ", "").to_i @product.current_price_rub = @agent.page.search('.product-discount-price').map(&:text).map(&:strip).uniq.first.gsub(" ", "").to_i @product.current_price_vnd = @product.current_price_rub * 330 + xxx000 @product.discount = @agent.page.search('.nohover').map(&:text).map(&:strip).uniq.first.split(" ").first.gsub("−", "").to_i @product.image_url = @agent.page.image_with(:src => /media\/products/).url.to_s diameter_position = @agent.page.search('.property-description-title').map(&:text).map(&:strip).map(&:strip).index("Диаметр:") + 1 @product.diameter = @agent.page.search("#product-cart > div.product-text-accordion-wrapper > div.product-text-accordion.first.opened > div > div.property-descriptions > div:nth-child(#{diameter_position}) > div.property-description-content").map(&:text).map(&:strip).uniq.first.to_i # @product.stock_status = agent.page.search('.js-product-cart-add').present? ? :stocking : :out_of_stock end end
end

Sau đó, mình chỉnh sửa một chút trong method create để có thể dùng thư viện Crawl lấy dữ liệu:

# /controllers/products_controller.rb def create @product = product.new(product_params) begin Services::Crawl.new(@product).perform! if @product.save redirect_to products_path, flash: { notice: 'Product was successfully created.' } else render :new end rescue Mechanize::ResponseCodeError => e Rails.logger.error "[Error][Mechanize::ResponseCodeError] #{e.message}" ... rescue => e Rails.logger.error "[Error] #{e.message}" ... end end

Thử tạo mới một mã sản phẩm và kết quả render tại trang danh sách sản phẩm:

# /views/products/index.html.erb <table class="table table-bordered"> <thead class="thead"> <tr class="text-center"> <th scope="col" class="text-center">Id</th> <th scope="col" class="text-center">Code</th> <th scope="col">Image</th> <th scope="col">Diameter</th> <th scope="col">Current Price (vnđ)</th> <th scope="col">Old Price (rub)</th> <th scope="col">Current Price (rub)</th> <th scope="col">Discount</th> <th colspan="3"></th> </tr> </thead> <tbody> <% @products.each do |product| %> <tr> <th scope="row"><%= @products.index(product) + 1 %></td> <th scope="row"><%= product.code %></td> <td><a type="button" data-toggle="modal" data-target="#exampleModalCenter" ><%= image_tag(product.image_url, style: "height: 160px;") %></a></td> <td><%= product.diameter %> mm</td> <td><%= product.current_price_vnd %> ₫</td> <td><%= product.old_price_rub %> ₽</td> <td><%= product.current_price_rub %> ₽</td> <td>−<%= product.discount %>%</td> <td> <%= link_to("https://example.net/catalog/product_#{product.code}.html", class: "btn btn-warning mb-3", target: "_blank") do %> <i class="fa fa-shopping-cart"></i> Detail <% end %> <%= link_to(product, method: :patch, data: { confirm: 'Are you sure?' }, class: "btn btn-success mb-3") do %> <i class="fa fa-arrow-up" aria-hidden="true""></i> Update <% end %> <%= link_to(product, method: :delete, data: { confirm: 'Are you sure?' }, class: "btn btn-danger") do %> <i class="fa fa-trash"></i> Delete <% end %> </td> </tr> <% end %> </tbody>
</table>

Tương tự cách trên, bạn có thể thêm các chức năng update, import csv, tạo job auto update cho trang dữ liệu crawl của bạn, chúc bạn thành công nhé!

Mình cũng có gặp một vấn đề với Mechanize là gem chỉ lấy được CSS và HTML của page mà không thể xử lý được JS. Mà tình trạng đơn hàng (trường stock_status) lại render sau khi trang A đã xử lý JS, do đó mình vẫn chưa thể lấy được giá trị cho trường stock_status (còn hay hết hàng). Vấn đề này mình có tìm hiểu thì do Mechanize thực tế cũng chỉ dùng nokorigi để xử lý html rồi từ đó cung cấp một cơ chế để ta có thể tương tác với trang web một cách đơn giản nhất. Và điểm hạn chế lớn nhất của nokorigi chính là nó không thể xử lý được javascript. Đây có thể xem như là một nhược điểm của gem này. Vậy nên nếu bạn muốn làm việc với một trang dùng JS thì nên cân nhắc việc lựa chọn một thư viện khác như Selenium, Capybara và PhantomJS...

Kết luận: Mechanize là một công cụ nhanh, gọn, dễ cài đặt để tương tác và crawl dữ liệu từ một web site, tuy nhiên nó lại không thể làm việc với JS nên cần cân nhắc trước khi lựa chọn có sử dụng cho ứng dụng của bạn hay không.

Một số method của Mechanize mà bạn có thể cần tham khảo: http://docs.seattlerb.org/mechanize/

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tôi cá là bạn không biết những điều này - Ruby on rails ( Phần 2)

Các bạn có thể theo dõi phần 1 ở đây :. https://viblo.asia/p/toi-ca-la-ban-khong-biet-nhung-dieu-nay-ruby-on-rails-phan-1-WAyK8DDeKxX. 5.

0 0 222

- vừa được xem lúc

Rails Memoization

Memoization is a process that can be used to speed up rails methods. It caches the results of methods that do time-consuming work, work that only needs to be done once. Here is an example. Example.

0 0 48

- vừa được xem lúc

Tại sao Rails lại dùng cả Webpack lẫn Sprocket?

Khi Rails 6 được ra mắt, có thể bạn đã từng tự hỏi. WTF, sao Webpack đã được add vào rồi, mà Sprocket vẫn tồn tại thế kia . Chẳng phải Webpack và Sprocket được dùng để giải quyết chung một công việc hay sao. Hoặc cả đây:.

0 0 59

- vừa được xem lúc

Bạn nên sử dụng Elasticsearch trong ứng dụng Ruby On Rails như thế nào?

Elasticsearch là một công cụ phân tích và mã nguồn mở mạnh mẽ và linh hoạt, phân tán, theo thời gian thực. Đó là tiêu chuẩn vàng trong công nghệ tìm kiếm.

0 0 80

- vừa được xem lúc

Form object pattern trong rails

1.Mở đầu.

0 0 111

- vừa được xem lúc

Sử dụng Twilio để gửi SMS trong ứng dụng Ruby on Rails

Ngoài cách xác nhận tài khoản hay gửi thông báo bằng email thì hôm nay mình sẽ hướng dẫn các bạn 1 cách nữa là thông qua SMS/Voice. Công cụ sử dụng sẽ là gem Twilio. Installation. Để cài đặt bằng Bundler, hãy lấy phiên bản mới nhất:.

0 0 67