Thử crawling dữ liệu Viblo bằng Mechanize gem

Thuật ngữ crawling đã quá quen thuộc với ae trong ngành chúng ta (nếu các bạn chưa biết crawling là gì thì xem qua ở đây nhé crawling), nge vậy thôi chứ làm việc với thằng này thì có lẽ ít người đã từng, chủ yếu là những ae làm về AI, thường craw data từ những forum cho con bot nó học. Hôm nay mình mạo muội thử crawling các bài viết trên trang Viblo và lưu nó vào file csv bằng ngôn ngữ ruby xem thế nào nhé. Bắt đầu thôi... 😄

Chuẩn bị

  • Ruby version: ruby 1.9.2 or newer
  • Qua một hồi tìm hiểu về crawling data bằng ruby thì mình thấy cũng khá là nhiều gem hỗ trợ cho việc này nhưng mình thấy mechanize gem hỗ trợ khá là đầy đủ và dễ dùng, vậy nên mình sẽ áp dụng mechanize gem cho công cuộc crawling data ở bài viết này.

Tìm hiểu về mechanize

1. Mechanize là gì?

  • Mechanize là một thư viện hỗ trợ cho việc tự động tương tác với website, mechanize cho phép lưu trữ và gửi cookie, cho phép links và submit form.

2. Tính năng của mechanize

Truy cập url
require 'rubygems'
require 'mechanize'

agent = Mechanize.new

page = agent.get('https://www..asia.com/')
Tương tác với đường dẫn(links) trong page
  • links Lấy danh sách các đường dẫn có trong page
    page.links.each do |link|
        puts link.text
    end
  • find tìm kiếm đường dẫn
# tìm kiếm đường dẫn có text là Newest
newest_link = agent.page.links.find { |l| l.text == 'Newest' }
  • link_with tìm kiếm với điều kiện
# tìm kiếm với option là text = Newest
newest_link = agent.page.link_with(text: 'Newest')

# tìm kiếm với option là href = /newest
newest_link = agent.page.link_with(href: '/newest')

# hoặc có thể kết hợp cả 2 hay nhiều option
newest_link = agent.page.link_with(text: 'Newest', href: '/newest')
Tương tác với form

Ở ví dụ này chúng ta thử search ở trên trang http://google.com/ xem nhé.

require 'rubygems'
require 'mechanize'

agent = Mechanize.new
page = agent.get('http://google.com/')

Để xem cấu trúc của page dưới format dễ nhìn thì chúng ta sử dụng pp

pp page

Bạn quan sát terminal sẽ thấy được một form với tên là 'f', trong form có một số filed và 2 button. Hãy thử tương tác với form bằng một số method sau nhé.

# truy suất form có tên là 'f'
google_form = page.form('f')

Mechanize hỗ trợ một số cách để để bạn có thể tương tác với input filed trong form nhưng cách thông dụng nhất đó là bạn có thể truy cập input field như một thuộc tính của một object. Trong ví dụ trên ta có input filed với name là q và cách tương tác với field này như sau:

google_form.q = 'Thử crawling dữ liệu Viblo bằng Mechanize gem'

Sau đó bạn thử print form ra màn hình console xem thành công chưa nhé

Và bây giờ thử nhấn btn search nhé

search_page = agent.submit(google_form)
pp search_page

Và file code hoàn chỉnh của ví dụ trên:

require 'rubygems'
require 'mechanize'

agent = Mechanize.new
page = agent.get('http://google.com/')
google_form = page.form('f')

# mình thêm force_encoding bởi vì các value search phải convert về  encode ISO-8859-1
google_form.q = 'Thử crawling dữ liệu Viblo bằng Mechanize gem'.force_encoding("ISO-8859-1")
search_page = agent.submit(google_form)
pp search_page

Ngoài ra mechanize còn hỗ trợ thêm một số hàm khác giúp bạn tương tác với form nhìu hơn như field_with, file_uploads. Ở bài viết này mình chỉ giới thiệu cho mn làm quen với mechanize nên không đi chi tiết. Để rõ hơn thì mn xem GUIDE_DOC nhé

Thực hành crawling các bài viết trên Viblo

Nội dung bài thực này chỉ đơn giản là crawling tất cả các bài viết có trên trang web Viblo và lưu vào file csv, thông tin crawling gồm có tên tác giả, tiêu đề bài viết và đường dẫn của bài viết.

1. Init project

# tạo folder
mkdir crawling

# di chuyển vào thư mục dự án
cd crawling

# tạo file Gemfile
touch Gemfile

# mở Gemfile và paste đoạn code phía dưới vào
source 'https://rubygems.org'

gem 'rspec', :require => 'spec' # để hỗ trợ viết test sau này
gem 'mechanize' # gem này chúng ta vừa tìm hiểu ở trên
gem 'pry', '~> 0.12.2' # dùng để debug

# quay lại màn hình terminal và chạy lệnh
bundle install

-> như vậy là chúng ta đã hoàn thành bước khởi tạo project, tiếp tục vào phần chính nhé :D

2. Triển khai crawling data

a) Khởi tạo service Crawler
require "mechanize"
require "pry"
require "csv"

class CrawlerService
  # HEADER chứa mảng các column name trong file csv
  HEADER = %w(Author\ name Title Url)
  # sử dụng để tìm kiếm button pagenation
  BASE_PAGINATE_URL = "/newest?page="
  # URL của trang web mình sẽ crawl data
  URL = "https://viblo.asia/newest"

  def initialize
    @agent = Mechanize.new
    @page_number = 1
  end
end
b) Viết hàm thực thi việc crawling data

Trước tiên mình xem qua cấu trúc DOM của trang viblo

Nhìn ở hình trên thì ta xác định được thông tin của một bài viết nằm trong thẻ div có class post-feed-item__info, vậy nên mình sẽ dùng search method để lấy tất cả các bài viết có trong page.

def perfom
  page = @agent.get URL

  puts "------------------"
  puts "Crawling.........."

 # tìm kiếm các bài viết
  page.search("div.post-feed-item__info").each do |post|
    puts post
  end

  puts "------------------"
  puts "Crawling finished."
end

Khi đã crawl được các bài viết, bây giờ chúng ta đi vào xem chi tiết cấu trúc DOM của từng bài để lấy những thông tin cần thiết nhé. Ở đây mình sẽ lấy author_name, title, url của bài viết.

Có 2 cách để bạn có thể truy suất dữ liệu của element:

  • Cách 1 truy suất thông qua thuộc tính children
def perfom
  ...
  
  page.search("div.post-feed-item__info").each do |post|
    author_name = post.children.first.children.first.children[2].children[1].children.text.strip
    title = post.children[2].children.first.children.first.children.first.text.strip
    url = post.children[2].children.first.children.first.attributes["href"].value.strip
  end

  ...
end

  • Cách 1 truy suất thông qua hàm search
def perfom
  ...
  
  page.search("div.post-feed-item__info").each do |post|
      author_name = post.search("a.mr-05").text.strip
      title = post.search("h3.word-break").last.text
      url = post.search("a.link").last.attributes["href"].value

      puts author_name
    
    puts author_name
  end

  ...
end

Kết quả thu được 😄

  • Tới đây thì con crawl của mình cũng tạm ổn rồi, tuy nhiên chúng mới chỉ chỉ crawl data ở trang đầu tiên, để crawl được hết các bài viết trên viblo thì chúng ta phải thêm chút mắm muối nữa nhé 😄

    -> Ý tưởng là mình sẽ click ở button phân trang ở phía dưới danh sách các bài viết, sau đó tiếp tục crawl và lặp lại cho đến page cuối cùng 😄

def perfom
  ...
  
  while true
      page.search("div.post-feed-item__info").each do |post|
        author_name = post.search("a.mr-05").text.strip
        title = post.search("h3.word-break").last.text
        url = post.search("a.link").last.attributes["href"].value

        puts author_name
      end

      # Tăng số page lên 1
      @page_number += 1
      # Tìm kiếm xem có còn page tiếp theo không
      next_page = page.link_with(href: BASE_PAGINATE_URL + @page_number.to_s)

      # trường hợp page không tồn tại hoặc số page > 3 thì mình dừng lại 
      # bởi vì bài viết trên viblo khá nhiều nên mình chỉ craw 3 page thôi :D
      # nếu muốn craw hết thì sửa đoạn dưới thành thế này nhé
      # break unless next_page
      break if next_page.nil? || @page_number > 3

     # click button paginate tiếp theo để sang trang tiếp theo
      page = next_page.click
    end
  ...
end

-> Các bạn thử run và xem thành quả nhé, code tham khảo mình push lên ở đây rồi nhé

c) Ghi dữ liệu đọc được vào file CSV
def perfom
   page = @agent.get URL

   puts "------------------"
   puts "Crawling.........."

   CSV.open("data.csv", "w", encoding: "UTF-8") do |csv|
     csv << HEADER

     while true
       page.search("div.post-feed-item__info").each do |post|
         author_name = post.search("a.mr-05").text.strip
         title = post.search("h3.word-break").last.text
         url = post.search("a.link").last.attributes["href"].value

         csv << [
           author_name,
           title,
           url
         ]
       end

       @page_number += 1
       next_page = page.link_with(href: BASE_PAGINATE_URL + @page_number.to_s)

       break if @page_number > 3

       page = next_page.click
     end
   end

   puts "------------------"
   puts "Crawling finished."
 end

-> Mn mở file data.csv để xem thành quả nhé. Nếu như có dữ liệu thì mn đã thành công rồi đó 👍💯

Bài viết hôm nay tạm ngưng ở đây thôi, các bạn có thể phát triển thêm project của mình để phục vụ cho mục đích của riêng mình như tạo thêm con bot cho nó làm việc này, setup cronjob cho nó,....

Source code: HERE

Cảm ơn mn đã đọc bài viết của mình. Có gì trao đổi thì cứ comment bài viết của mình để cùng trao đổi nhé 🤝

Tham khảo


All Rights Reserved