Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Ruby

Xây dựng Trình Thu thập Thông tin Trang Web Đầu tiên Của bạn: Phần 3

by
Length:LongLanguages:

Vietnamese (Tiếng Việt) translation by Dai Phong (you can also view the original English article)

Chào mừng quay trở lại với loạt bài về xây dựng một trình thu thập thông tin trang web. Trong hướng dẫn này, tôi sẽ đi qua một ví dụ về thu thập dữ liệu từ trang podcast của tôi. Tôi sẽ khái quát chi tiết cách tôi trích xuất dữ liệu, các phương thức trợ giúp và các tiện ích thực hiện công việc của chúng như thế nào, và làm thế nào để kết hợp tất cả các phần lại với nhau.

Các chủ đề

  • Thu thập thông tin từ trang Podcast của tôi
  • Pry
  • Scraper
  • Các Phương thức Trợ giúp
  • Ghi các bài viết

Thu thập thông tin từ trang Podcast của tôi

Hãy áp dụng những gì chúng ta đã học được cho đến lúc này vào thực tế. Vì nhiều lý do khác nhau, một thiết kế lại cho trang podcast Between | Screens của tôi đã trở nên quá dài. Có những vấn đề khiến tôi phải thét lên khi tôi tỉnh dậy vào buổi sáng. Vì vậy, tôi đã quyết định thiết lập một trang web tĩnh hoàn toàn mới, được xây dựng với Middleman và được lưu trữ trên GitHub Pages.

Tôi đã giành rất nhiều thời gian vào thiết kế mới sau khi tôi đã tinh chỉnh một blog Middleman để đáp ứng nhu cầu của tôi. Còn lại, tất cả những gì phải làm là nhập nội dung của tôi từ ứng dụng Sinatra được hỗ trợ bởi cơ sở dữ liệu, vì vậy tôi cần phải thu thập nội dung hiện có và chuyển nó vào trang tĩnh mới của tôi.

Làm việc này bằng tay thì chắc là không rồi - thậm chí không cần phải hỏi - vì tôi có thể tin tưởng giao cho những người bạn Nokogiri và Mechanize làm việc này cho tôi. Phía trước bạn là một trình thu thập thông tin nhỏ, hợp lý, không quá phức tạp nhưng cung cấp một vài điều thú vị mà những người mới nên nghiên cứu.

Dưới đây là hai ảnh chụp màn hình từ trang podcast của tôi.

Ảnh chụp màn hình trang podcast cũ

A screenshot of an old podcast

Ảnh chụp màn hình trang podcast mới

A screenshot of a new podcast

Hãy chia nhỏ những gì chúng ta muốn thực hiện. Chúng ta muốn trích xuất những dữ liệu sau đây từ 139 tập được chia thành 21 trang:

  • tiêu đề
  • người được phỏng vấn
  • tiêu đề phụ cùng với danh sách các chủ đề
  • số bài SoundCloud cho mỗi tập
  • ngày
  • số tập
  • văn bản từ phần ghi chú của chương trình
  • các liên kết từ phần ghi chú của chương trình

Chúng ta lặp qua phân trang và để cho Mechanize nhấp vào mỗi liên kết cho một tập. Ở trên trang chi tiết sau đó, chúng ta sẽ tìm kiếm tất cả các thông tin mà chúng ta cần như được liệt kê ở trên. Bằng dữ liệu đã được trích xuất, chúng ta cần điền chúng vào front-matter và phần thân của các tập tin markdown cho mỗi tập.

Dưới đây bạn có thể thấy bản xem trước về cách chúng ta sẽ soạn ra các tập tin markdown mới bằng nội dung mà chúng ta đã trích xuất được. Tôi nghĩ rằng điều này sẽ mang lại cho bạn một ý tưởng hay về những việc đang ở trước mắt chúng ta. Điều này biểu diễn cho bước cuối cùng trong script nhỏ của chúng ta. Đừng lo, chúng ta sẽ khái quát nó chi tiết hơn.

def compose_markdown

Tôi còn muốn thêm một vài thủ thuật mà trang web cũ không thể làm được. Việc có sẵn một hệ thống gắn thẻ (tag) tuỳ biến và toàn diện là yếu tố cốt lõi đối với tôi. Tôi muốn người nghe có một công cụ khám phá sâu sắc. Vì vậy, tôi cần gắn thẻ cho từng người được phỏng vấn và đồng thời phân chia tiêu đề phụ thành các thẻ. Vì một mình tôi đã xuất bản được 139 tập trong mùa đầu tiên, nên tôi phải chuẩn bị cho trang web khi lượng nội dung trở nên khó khăn hơn để đọc. Một hệ thống gắn thẻ (tag) sâu với các đề xuất thông minh là những gì mà tôi hướng đến. Việc này cho phép tôi giữ cho trang web nhẹ và nhanh.

Hãy xem code hoàn chỉnh dùng để trích xuất nội dung từ trang web của tôi. Hãy xem kỹ và cố gắng hình dung về những gì đang diễn ra. Vì tôi giả sử bạn là người mới bắt đầu, nên tôi đã tránh không trừu tượng quá mức và thiên về rõ ràng. Tôi đã tái cấu trúc một số nhằm làm cho code trở nên rõ ràng, nhưng tôi cũng để lại một ít để bạn thực hành khi hoàn tất bài viết này. Nhìn chung, việc học trở nên hiệu quả khi bạn không chỉ đọc mà còn thực hành với một số code của riêng bạn.

Song song đó, tôi rất khuyến khích bạn bắt đầu suy nghĩ về cách bạn có thể cải tiến các code ở trước mặt bạn. Đây sẽ là nhiệm vụ sau cùng của bạn ở cuối của bài viết này. Một gợi ý nhỏ từ tôi: chia nhỏ các phương thức lớn thành những phương thức nhỏ hơn luôn là một khởi đầu tốt. Một khi bạn hiểu được cách các code đó làm việc, thì bạn sẽ cảm thấy húng thú với việc tái cấu trúc đó.

Tôi bắt đầu bằng cách trích xuất một loạt các phương thức thành những hàm trợ giúp nhỏ, tập trung hơn. Bạn sẽ dễ dàng có thể áp dụng những gì bạn đã học được từ các bài viết trước đây của tôi về code xấu và tái cấu trúc chúng. Nếu việc này làm bạn choáng ngợp, xin đừng lo lắng - đã có chúng tôi ở đây. Chỉ cần kiên nhẫn, đến một lúc nào đó mọi thứ sẽ trở nên dễ hiểu.

Code đầy đủ

Tại sao chúng ta không require "Nokogiri"? Mechanize đã cung cấp cho chúng ta tất cả các nhu cầu thu thập của chúng ta. Như chúng ta đã thảo luận trong bài viết trước, Mechanize được xây dựng dựa trên Nokogiri và cũng cho phép chúng ta trích xuất nội dung. Tuy nhiên, điều quan trọng là khái quát gem đó trong bài viết đầu tiên vì chúng ta cần phải xây dựng dựa trên nó.

Pry

Trước tiên. Trước khi nhảy ngay vào code của chúng ta ở đây, tôi nghĩ rằng cần phải cho bạn thấy làm thế nào bạn có thể kiểm tra một cách hiệu quả code của bạn có hoạt động như mong đợi ở mỗi bước hay không. Chắc bạn cũng đã nhận thấy, tôi đã thêm vào một công cụ. Bên cạnh những thứ khác, Pry thật sự là công cụ tiện dụng để gỡ lỗi.

Nếu bạn đặt Pry.start(binding) ở bất cứ nơi nào trong code của bạn, bạn có thể phân tích ứng dụng của bạn một cách chính xác tại điểm đó. Bạn có thể pry vào các đối tượng tại các thời điểm cụ thể trong ứng dụng. Nó thật sự hữu ích để chạy ứng dụng của bạn từng bước mà không sợ bị lỗi. Ví dụ, hãy đặt nó ngay sau hàm write_page và kiểm tra xem link có phải là những gì chúng ta mong đợi hay không.

Pry

Nếu bạn chạy script, chúng ta sẽ nhận được một số thứ như sau.

Đầu ra

Sau đó, khi chúng ta yêu cầu đối tượng link, chúng ta có thể kiểm tra xem chúng ta có lấy đúng bài hay không trước khi chuyển sang các cài đặt khác.

Terminal

Có vẻ như những gì chúng ta cần. Tuyệt vời, chúng ta có thể tiếp tục. Thực hiện việc này từng bước trên toàn bộ ứng dụng là một cách làm quan trọng để đảm bảo rằng bạn không bị mất phương hướng và bạn thật sự hiểu cách nó hoạt động. Tôi sẽ không khái quát chi tiết Pry ở đây vì nó sẽ cần ít nhất là một bài viết đầy đủ. Tôi chỉ có thể khuyên bạn nên sử dụng nó như là một sự thay thế cho shell IRB tiêu chuẩn. Trở lại với công việc chính của chúng ta.

Scraper

Bây giờ bạn đã có cơ hội để tự mình làm quen với các mảnh ghép đang có, tôi đề xuất rằng chúng ta hãy khái quát từng cái một và làm rõ một số điểm thú vị. Hãy bắt đầu với các phần quan trọng.

podcast_scraper.rb

Chuyện gì xảy ra trong phương thức scrape thế? Trước tiên, tôi lặp qua mọi trang index trong podcast cũ. Tôi sử dụng URL cũ từ ứng dụng Heroku vì trang web mới đã sẵn sàng tại betweenscreens.fm. Tôi có 20 trang chứa các tập mà tôi cần phải lặp qua.

Tôi đặt giới hạn cho vòng lặp thông qua biến link_range, cái mà tôi cập nhật với mỗi vòng lặp. Duyệt qua phân trang cũng đơn giản như việc sử dụng biến này trong URL của mỗi trang. Đơn giản và hiệu quả.

def scrape

Sau đó, bất cứ khi nào tôi có được một trang mới cùng với tám tập khác để trích xuất, tôi sử dụng page.links để xác định các liên kết mà chúng ta muốn nhấp vào và truy cập vào trang chi tiết cho mỗi tập. Tôi quyết định sử dụng một dãy các liên kết (links[2..8]) vì nó cố định trên mỗi trang. Nó cũng là cách dễ nhất để chọn các liên kết mà tôi cần từ mỗi trang index. Không cần phải mò mẫm với các bộ chọn CSS ở đây.

Sau đó chúng ta đưa liên kết dẫn đến trang chi tiết đó vào phương thức write_page. Đây là nơi hầu hết các công việc được thực hiện. Chúng ta lấy liên kết đó, nhấp vào nó và đi theo nó tới trang chi tiết nơi chúng ta có thể bắt đầu trích xuất dữ liệu của nó. Trên trang này, chúng ta tìm kiếm tất cả các thông tin mà tôi cần để soạn ra các tập tin markdown mới cho trang web mới.

def write_page

def extract_data

Như bạn có thể quan sát thấy ở trên, chúng ta lấy detail_page đó và áp dụng một loạt các phương thức trích xuất trên nó. Chúng ta trích xuất interviewee, title, sc_id, text, episode_titleepisode_number. Tôi đã tái cấu trúc một loạt các phương thức trợ giúp chịu trách nhiệm cho việc trích xuất này. Hãy cùng tìm hiểu sơ qua chúng:

Các Phương thức Trợ giúp

Các Phương thức Trích xuất

Tôi trích ra những phương thức trợ giúp này bởi vì nó cho phép tôi có các phương thức nhỏ hơn. Đóng gói những hành vi của chúng cũng là điều quan trọng. Code cũng trở nên dễ đọc hơn. Hầu hết chúng nhận detail_page như là một đối số và trích xuất một số dữ liệu cụ thể mà chúng ta cần cho các bài viết Middleman của chúng ta.

Chúng ta tìm kiếm một bộ chọn cụ thể ở trên trang và lấy về văn bản mà không có những khoảng trắng không cần thiết.

Chúng ta lấy tiêu đề và xóa ?# vì chúng không phù hợp với front-matter trong các bài viết cho các tập của chúng ta. Tìm hiểu thêm về front-matter ở bên dưới đây.

Ở đây chúng ta cần phải làm việc nhiều hơn một chút để trích xuất id SoundCloud cho các bài được lưu trữ của chúng ta. Trước tiên, chúng ta cần Mechanize tạo iframe với href của soundcloud.com và tạo một chuỗi để quét...

Sau đó khớp một biểu thức chính quy cho các chữ số id của nó - soundcloud_id "221003494" của chúng ta.

Việc trích xuất các ghi chú chương trình một lần nữa khá đơn giản. Chúng ta chỉ cần tìm ra các đoạn văn của ghi chú chương trình trong trang chi tiết. Không có gì đáng ngạc nhiên ở đây cả.

Tương tự cho tiêu đề phụ, ngoại trừ rằng nó chỉ là một sự chuẩn bị để trích xuất số tập một cách sạch sẽ từ nó.

Ở đây chúng ta cần một biểu thức chính quy khác. Hãy xem trước và sau khi chúng ta áp dụng regex.

episode_subtitle

số

Một bước nữa cho đến khi chúng ta có được một con số sạch sẽ.

Chúng ta lấy con số đó với một dấu # và xoá bỏ nó. Ổn rồi, chúng ta cũng có số tập đầu tiên được trích xuất là 139. Tôi đề nghị chúng ta cũng nên tìm hiểu các phương thức tiện ích khác trước khi chúng ta kết hợp tất cả chúng lại với nhau.

Các Phương thức Tiện ích

Sau tất cả các công việc trích xuất, chúng ta cần dọn dẹp một số. Chúng ta có thể sẵn sàng bắt đầu chuẩn bị dữ liệu cho việc soạn ra các tập tin markdown. Ví dụ, tôi cắt episode_subtitle ra một số chi tiết để có được một ngày tháng sạch sẽ và xây dựng tags với phương thức build_tags.

def clean_date

Chúng ta chạy một regex khác để tìm kiếm ngày ví dụ như: " Aug 26, 2015". Như bạn có thể thấy, điều này vẫn chưa hữu ích cho lắm. Từ string_date mà chúng ta nhận được từ tiêu đề phụ, chúng ta cần tạo một đối tượng Date thật sự. Nếu không nó sẽ vô dụng đối với việc tạo ra các bài cho Middleman.

string_date

Vì vậy, chúng ta lấy chuỗi đó và áp dụng Date.parse. Kết quả trông khả quan hơn rồi đấy.

Date

def build_tags

Phương thức này nhận titleinterviewee mà chúng ta đã xây dựng bên trong phương thức extract_data và xoá bỏ tất cả các ký tự không cần thiết. Chúng ta thay thế các ký tự đó bằng một dấu phẩy, @,?, #, và & bằng một chuỗi rỗng, và cuối cùng xử lý các chữ viết tắt bằng with.

def strip_pipes

Cuối cùng, chúng ta còn phải bao gồm tên người được phỏng vấn trong danh sách thẻ và phân chia từng thẻ bằng dấu phẩy.

Trước

Sau

Mỗi thẻ sẽ trở thành một liên kết đến một tập hợp các bài viết cho chủ đề đó. Tất cả những điều này xảy ra bên trong phương thức extract_data. Hãy xem lại chúng ta đang ở đâu nhé:

def extract_data

Tất cả những gì còn lại cần phải làm là trả về một hash options chứa các dữ liệu mà chúng ta đã trích xuất. Chúng ta có thể đưa hash này vào phương thức compose_markdown, phương thức mà sẽ lấy dữ liệu sẵn có để soạn nên các tập tin mà tôi cần cho trang web mới.

Soạn các bài viết

def compose_markdown

Để xuất bản các tập podcast trên trang web Middleman của tôi, tôi đã tinh chỉnh lại mục đích cho hệ thống blog của nó. Thay vì đơn thuần tạo ra các bài blog, tôi tạo các ghi chú cho các tập của tôi hiển thị các tập được lưu trên SoundCloud thông qua iframe. Trên các trang index, tôi chỉ hiển thị iframe đó cùng với tiêu đề và các thứ.

Định dạng mà tôi cần để nó hoạt động bao gồm một thứ được gọi là front-matter. Cơ bản đây là một cặp khoá/giá trị cho các trang web tĩnh của tôi. Nó thay thế cơ sở dữ liệu từ trang web Sinatra cũ của tôi.

Các dữ liệu như tên người được phỏng vấn, ngày tháng, id SoundCloud, số tập và vân vân nằm giữa ba dấu gạch ngang (---) ở trên cùng của tập tin. Bên dưới là nội dung cho từng tập - những thứ như câu hỏi, liên kết, tài trợ, v.v.

Front-Matter

Trong phương thức compose_markdown, tôi sử dụng một HEREDOC để soạn tập tin đó với front-matter của nó cho mỗi tập mà chúng ta lặp qua. Từ hash options mà chúng ta cung cấp cho phương thức này, chúng ta trích xuất tất cả các dữ liệu mà chúng ta thu thập được trong phương thức trợ giúp extract_data.

def compose_markdown

Đây là cái sườn cho một tập podcast mới. Đây là những gì chúng ta đã hướng đến. Có thể bạn đang thắt mắt về cú pháp đặc biệt này: #{options[:interviewee]}. Tôi thêm chuỗi vào như bình thường, nhưng vì tôi đã ở bên trong một <<-HEREDOC, nên tôi có thể lược bỏ dấu ngoặc kép.

Bình tĩnh lại, chúng ta vẫn đang ở trong vòng lặp, bên trong hàm write_page cho mỗi liên kết được nhấp vào dẫn đến trang chi tiết với các ghi chú chương trình của một tập cụ thể. Những gì xảy ra tiếp theo là chuẩn bị ghi cái sườn này vào hệ thống tập tin. Nói cách khác, chúng ta tạo ra bài viết thật sự bằng cách cung cấp tên tập tin và markdown_text đã được soạn.

Đối với bước cuối cùng đó, chúng ta chỉ cần chuẩn bị các thành phần sau đây: ngày tháng, tên người được phỏng vấn, và số tập. Tất nhiên cùng với markdown_text, cái mà chúng ta vừa có được từ compose_markdown.

def write_page

Sau đó chúng ta chỉ cần lấy file_namemarkdown_text và ghi thành tập tin.

def write_page

Tương tự hãy chia nhỏ hàm này ra. Đối với mỗi bài viết, tôi cần một định dạng cụ thể: một thứ gì đó tương tự như 2016-10-25-Avdi-Grimm-120. Tôi muốn ghi ra các tập tin bắt đầu bằng ngày và bao gồm tên người được phỏng vấn và số tập.

Để phù hợp với định dạng mà Middleman yêu cầu cho các bài viết mới, tôi cần lấy tên người được phỏng vấn và truyền nó vào phương thức trợ giúp dasherize, sau đó từ Avdi Grimm chuyển thành Avdi-Grimm. Không có ma thuật nào ở đây cả, nhưng rất đáng để tìm hiểu:

def dasherize

Nó loại bỏ khoảng trắng khỏi văn bản mà chúng ta trích ra tên người được phỏng vấn và thay thế khoảng trắng giữa Avdi và Grimm bằng một dấu gạch ngang. Phần còn lại của tên tập tin được nối liền bằng các dấu gạch dưới: "date-interviewee-name-episodenumber".

def write_page

Vì nội dung được trích xuất thẳng từ một trang HTML, nên tôi không thể chỉ sử dụng .md hoặc .markdown như là phần mở rộng của tên tập tin. Tôi quyết định đi kèm với .html.erb.md. Đối với các tập tin tôi soạn ra sau này mà không cần trích xuất, tôi có thể lược bỏ phần .html.erb và chỉ cần .md.

Sau bước này, vòng lặp trong hàm scrape sẽ kết thúc và chúng ta sẽ có một tập trông như sau:

2014-12-01-Avdi-Grimm-1.html.erb.md

Tất nhiên, scraper này sẽ bắt đầu ở tập cuối, và lặp cho đến tập đầu tiên. Để minh hoạ, thì tập 01 cũng tương tự như những tập khác. Bạn có thể thấy front-matter cùng với các dữ liệu mà chúng ta đã trích xuất.

Trước đó, tất cả những thứ này bị khóa trong cơ sở dữ liệu của ứng dụng Sinatra - số tập, ngày tháng, tên người được phỏng vấn và vân vân. Bây giờ chúng ta đã chuẩn bị nó xong xui để trở thành một phần của trang web tĩnh Middleman mới của tôi. Mọi thứ ở bên dưới hai bộ ba dấu gạch ngang (---) là văn bản từ ghi chú của chương trình: chủ yếu là các câu hỏi và liên kết.

Phần Tóm lược

Và chúng ta đã hoàn tất công việc. Trang podcast mới của tôi đã hoạt động. Tôi thật sự vui mừng vì tôi đã dành thời gian để thiết kế lại những thứ này từ đầu đến cuối. Rất muốn xuất bản các tập mới ngay lúc này. Việc khám phá nội dung mới cũng sẽ mượt mà hơn cho người dùng.

Như tôi đã đề cập ở trên, đây là lúc bạn nên mở code editor để thực hành. Hãy lấy code này và vật lộn với nó một chút nhé. Thử tìm cách để làm cho nó đơn giản hơn. Có nhiều chỗ để bạn có thể tái cấu trúc code.

Nhìn chung, tôi hy vọng ví dụ nhỏ này sẽ cho bạn một ý tưởng rõ ràng về những gì bạn có thể làm với công cụ thu thập dữ liệu trang web mới của bạn. Tất nhiên bạn có thể làm được những thứ phức tạp hơn - tôi chắc chắn rằng có rất nhiều thứ có thể được thực hiện với những kỹ năng này.

Nhưng như thường lệ, hãy làm từng bước một và đừng chán nản nếu mọi thứ không êm xui. Điều này không chỉ bình thường đối với hầu hết mọi người mà còn là điều có thể đoán trước được. Nó là một phần của cuộc hành trình. Chúc bạn vui vẻ!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.