Mục lục
- Web Scraping là gì?
- Quyền truy cập
- Vấn đề cần giải quyết
- Nokogiri
- Trích xuất?
- Pages
- API
- Điều hướng Node
Web Scraping là gì?
Có nhiều thuật ngữ hoa mỹ hơn "web scraping" hay "screen scraping". "Web harvesting" (thu hoạch web) và "web data extraction" (trích xuất dữ liệu web) nói lên ngay bản chất vấn đề. Chúng ta có thể tự động hóa việc trích xuất dữ liệu từ các trang web – và nó không phức tạp như bạn nghĩ.
Về mặt nào đó, các công cụ này cho phép bạn mô phỏng và tự động hóa việc duyệt web của con người. Bạn viết chương trình chỉ trích xuất loại dữ liệu bạn quan tâm. Nhắm mục tiêu dữ liệu cụ thể gần như dễ dàng như dùng CSS selector.
Vài năm trước, tôi đăng ký khóa học video trực tuyến có hàng triệu video ngắn nhưng không có tùy chọn tải xuống hàng loạt. Tôi phải đi qua từng link và làm thủ công "save as" khổ sở. Đó là một dạng "web scraping bằng tay" – thứ chúng ta thường phải làm khi thiếu kiến thức tự động hóa. Khóa học ổn, nhưng sau đó tôi không dùng dịch vụ của họ nữa. Quá mệt mỏi!
Hôm nay, tôi không quan tâm nhiều đến UX khiến não bộ tan chảy như vậy. Một scraper tải xuống cho tôi chỉ mất vài phút để viết. Không vấn đề gì!
Để tôi phân tích nhanh trước khi bắt đầu. Toàn bộ quy trình có thể rút gọn thành vài bước. Trước tiên, chúng ta tải trang web chứa dữ liệu mong muốn. Sau đó, tìm kiếm qua trang đó và xác định thông tin cần trích xuất.
Bước cuối là nhắm mục tiêu các mảnh dữ liệu đó, cắt lát nếu cần, và quyết định lưu trữ như thế nào và ở đâu. HTML viết tốt thường là chìa khóa khiến quá trình này dễ dàng và thú vị. Đối với trích xuất phức tạp hơn, nó có thể đau đầu nếu phải xử lý markup cấu trúc kém.
API thì sao? Câu hỏi hay. Nếu bạn có quyền truy cập dịch vụ có API, thường không cần viết scraper riêng. Cách tiếp cận này chủ yếu dành cho các trang web không cung cấp sự tiện lợi đó. Không có API, đây thường là cách duy nhất để tự động hóa trích xuất thông tin từ website.
Bạn có thể hỏi, scraping thực sự hoạt động như thế nào? Không đi sâu vào chi tiết, câu trả lời ngắn gọn: bằng cách duyệt cấu trúc dữ liệu cây. Nokogiri xây dựng các cấu trúc dữ liệu này từ tài liệu bạn cung cấp và cho phép bạn nhắm mục tiêu các phần quan tâm để trích xuất. Ví dụ, CSS là ngôn ngữ viết cho việc duyệt cây, tìm kiếm cấu trúc dữ liệu cây, và chúng ta có thể tận dụng nó cho trích xuất dữ liệu.
Có nhiều cách tiếp cận và giải pháp để thử. Rubyland có hai gem chiếm据 spotlight vài năm nay. Nhiều người vẫn tin dùng Nokogiri và Mechanize cho nhu cầu scraping HTML. Cả hai đã được kiểm chứng, dễ dùng và khả năng cao. Chúng ta sẽ xem cả hai. Nhưng trước đó, tôi muốn dành chút thời gian nói về vấn đề chúng ta sẽ giải quyết ở cuối serie giới thiệu ngắn này.
Quyền truy cập
Trước khi bắt đầu scraping, hãy chắc chắn bạn có quyền truy cập của các trang web bạn muốn lấy dữ liệu. Nếu trang có API hoặc RSS feed, ví dụ, nó không chỉ dễ dàng hơn để lấy nội dung mong muốn, mà có thể là lựa chọn hợp pháp.
Không phải ai cũng thích nếu bạn scraping lớn trên trang của họ – điều dễ hiểu. Hãy tự trang bị kiến thức về trang web cụ thể bạn quan tâm, và đừng tự rước rắc. Xác suất gây thiệt hại nghiêm trọng thấp, nhưng mạo hiểm không biết là không nên.
Vấn đề cần giải quyết
Tôi cần xây dựng podcast mới. Thiết kế chưa như ý, và tôi ghét cách đăng bài mới. Chết tiệt WYSIWYG! Chút bối cảnh: Khoảng 2 năm trước, tôi xây dựng phiên bản đầu tiên của podcast. Ý tưởng là chơi với Sinatra và xây dựng thứ gì đó siêu nhẹ. Tôi gặp vài vấn đề bất ngờ vì tự làm hầu như mọi thứ.
Đến từ Rails, chắc chắn là hành trình học hỏi quý giá mà tôi trân trọng, nhưng tôi nhanh chóng hối hận vì không dùng static site có thể deploy qua GitHub Pages. Việc deploy tập mới và duy trì thiếu sự đơn giản tôi tìm kiếm. Một thời gian, tôi quyết định có "cá lớn hơn để chiên" và tập trung sản xuất nội dung podcast.
Mùa hè vừa rồi, tôi bắt đầu nghiêm túc và làm việc trên trang Middleman host qua GitHub Pages. Cho mùa 2, tôi muốn thứ mới mẻ. Thiết kế đơn giản hóa, Markdown để đăng tập mới, và không còn "đấu súng" với Heroku – thiên đường! Vấn đề là tôi có 139 tập cũ cần import và chuyển đổi trước để làm việc với Middleman.
Đối với bài viết, Middleman dùng file .markdown có frontmatter cho dữ liệu – thay thế database cơ bản. Làm thủ công với 139 tập không phải lựa chọn. Đó là lúc tính toán phát huy tác dụng. Tôi cần cách parse HTML trang cũ, scraping nội dung liên quan, và chuyển sang bài viết blog để đăng podcast trên Middleman.
Do đó, qua 3 bài tới, tôi sẽ giới thiệu công cụ thường dùng trong Rubyland cho tác vụ này. Cuối cùng, chúng ta sẽ xem giải pháp thực tế của tôi.
Nokogiri
Ngay cả khi bạn hoàn toàn mới với Ruby/Rails, khả năng cao bạn đã nghe về gem nhỏ này. Tên nó thường được nhắc đến và dễ nhớ. Tôi không chắc nhiều người biết "nokogiri" trong tiếng Nhật nghĩa là "cái cưa".
Tên gọi phù hợp khi bạn hiểu công cụ này làm gì. Tác giả gem là Tenderlove, Aaron Patterson. Nokogiri chuyển đổi tài liệu XML và HTML thành cấu trúc dữ liệu – chính xác hơn là cấu trúc dữ liệu cây. Công cụ nhanh và cung cấp interface đẹp. Tổng thể, đây là thư viện mạnh mẽ giải quyết đa số nhu cầu scraping HTML của bạn.
Bạn có thể dùng Nokogiri không chỉ để parse HTML; XML cũng được. Nó cung cấp cả XPath (XML Path Language) và CSS interface để duyệt tài liệu bạn tải. XPath là ngôn ngữ truy vấn cho phép chọn node từ tài liệu XML. CSS selector quen thuộc hơn với người mới. Giống như viết style, CSS selector giúp dễ dàng nhắm mục tiêu các phần cụ thể của trang để trích xuất. Bạn chỉ cần cho Nokogiri biết bạn muốn gì khi nhắm đến đích cụ thể.
Pages
Chúng ta luôn cần bắt đầu bằng cách tải trang thực sự quan tâm. Chúng ta chỉ định loại tài liệu Nokogiri muốn parse – XML hoặc HTML:
Nokogiri::XML Nokogiri::HTML
some_scraper.rb
require "nokogiri"
require "open-uri"
page = Nokogiri::XML(File.open("some.xml"))
page = Nokogiri::HTML(File.open("some.html"))
Nokogiri::XML và Nokogiri::HTML chấp nhận đối tượng IO hoặc String. Điều xảy ra ở trên rất trực quan. Mở và tải trang chỉ định dùng open-uri sau đó load cấu trúc XML/HTML vào tài liệu Nokogiri mới. XML không phải thứ người mới thường gặp. Do đó, tôi khuyên tập trung vào parse HTML cho lúc này. Tại sao open-uri? Module này từ Ruby Standard Library cho phép lấy trang web без phiền toái. Vì đối tượng IO được chấp nhận, chúng ta dễ dàng dùng open-uri.
API
Hãy thực hành với ví dụ nhỏ:
at_css
some_podcast_scraper.rb
require 'nokogiri'
require "open-uri"
url = 'https://betweenscreens.fm/'
page = Nokogiri::HTML(open(url))
header = page.at_css("h2.post-title")
title = header.text
puts "This is the raw header of the latest episode: #{header}"
puts "This is the title of the latest episode: #{title}"
Điều chúng ta làm ở đây đại diện cho tất cả các bước thường gặp trong web scraping – chỉ ở mức vi mô. Chúng ta quyết định URL cần thiết, tải trang, load vào tài liệu Nokogiri mới. Sau đó mở trang và nhắm mục tiêu phần cụ thể.
Ở đây tôi chỉ muốn biết tiêu đề tập mới nhất. Dùng phương thức at_css và CSS selector cho h2.post-title là đủ để nhắm điểm trích xuất. Với phương thức này, chúng ta chỉ scrape phần tử đơn lẻ. Nó cho cả selector – hầu như không phải thứ chúng ta cần. Do đó, trích xuất chỉ phần văn bản bên trong node qua phương thức text. Để so sánh, xem đầu ra cho cả header và text bên dưới.
Đầu ra
This is the raw title of the latest episode: <h2 class="post-title"><a href="episodes/142/">David Heinemeier Hansson</a></h2> This is the title of the latest episode: David Heinemeier Hansson
Mặc dù ví dụ này ứng dụng hạn chế, nhưng nó chứa tất cả nguyên liệu, tất cả bước bạn cần hiểu. Tôi thấy cool vì sự đơn giản. Vì có thể không rõ từ ví dụ này, tôi muốn chỉ ra công cụ này mạnh đến đâu. Hãy xem chúng ta còn làm gì nữa với script Nokogiri.
Chú ý!
Nếu bạn là người mới và không chắc cách nhắm HTML cần thiết, tôi khuyên tìm online cách kiểm tra nội dung trang web trong trình duyệt. Gần như tất cả trình duyệt lớn làm điều này rất dễ dàng hiện nay.
Trên Chrome, bạn chỉ cần right-click vào phần tử trên trang và chọn inspect. Sẽ mở cửa sổ nhỏ ở dưới trình duyệt hiển thị giống như "X-quang" DOM của trang. Nó có nhiều tùy chọn hơn, tôi khuyên dành thời gian Google để tự học. Thời gian dành cho việc này rất đáng giá!
css
Phương thức css sẽ cho chúng ta không chỉ một phần tử đơn lẻ mà bất kỳ phần tử nào khớp tiêu chí tìm kiếm trên trang. Rất gọn gàng và trực quan!
some_scraper.rb
require 'nokogiri'
require "open-uri"
url = 'https://betweenscreens.fm/'
page = Nokogiri::HTML(open(url))
headers = page.css("h2.post-title")
headers.each do |header|
puts "This is the raw title of the latest episode: #{header}"
end
headers.each do |header|
puts "This is the title of the latest episode: #{header.text}"
end
Đầu ra
This is the raw title of the latest episode: <h2 class="post-title"><a href="episodes/142/">David Heinemeier Hansson</a></h2> This is the raw title of the latest episode: <h2 class="post-title"><a href="episodes/141/">Zach Holman</a></h2> This is the raw title of the latest episode: <h2 class="post-title"><a href="episodes/140/">Joel Glovier</a></h2> This is the raw title of the latest episode: <h2 class="post-title"><a href="episodes/139/">João Ferreira</a></h2> This is the raw title of the latest episode: <h2 class="post-title"><a href="episodes/138/">Corwin Harrell</a></h2> This is the raw title of the latest episode: <h2 class="post-title"><a href="episodes/137/">Roberto Machado</a></h2> This is the raw title of the latest episode: <h2 class="post-title"><a href="episodes/136/">James Edward Gray II</a></h2> This is the title of the latest episode: David Heinemeier Hansson This is the title of the latest episode: Zach Holman This is the title of the latest episode: Joel Glovier This is the title of the latest episode: João Ferreira This is the title of the latest episode: Corwin Harrell This is the title of the latest episode: Roberto Machado This is the title of the latest episode: James Edward Gray II
Khác biệt nhỏ trong ví dụ này là tôi lặp qua raw headers trước. Tôi cũng trích xuất inner text bằng phương thức text. Nokogiri tự động dừng ở cuối trang và không tự động theo dõi phân trang.
Giả sử chúng ta muốn thêm thông tin, ví dụ ngày và phụ đề cho mỗi tập. Chúng ta chỉ cần mở rộng ví dụ trên. Là ý tưởng tốt để làm từng bước. Làm một mảnh nhỏ chạy được rồi thêm độ phức tạp dần.
some_scraper.rb
require 'nokogiri'
require "open-uri"
url = 'https://betweenscreens.fm/'
page = Nokogiri::HTML(open(url))
articles = page.css("article.index-article")
articles.each do |article|
header = article.at_css("h2.post-title")
date = article.at_css(".post-date")
subtitle = article.at_css(".topic-list")
puts "This is the raw header: #{header}"
puts "This is the raw date: #{date}"
puts "This is the raw subtitle: #{subtitle}\n\n"
puts "This is the text header: #{header.text}"
puts "This is the text date: #{date.text}"
puts "This is the text subtitle: #{subtitle.text}\n\n"
end
Đầu ra
This is the raw header: <h2 class="post-title"><a href="episodes/142/">David Heinemeier Hansson</a></h2> This is the raw date: <span class="post-date">Oct 18 | 2016</span> This is the raw subtitle: <h3 class="topic-list">Rails community | Tone | Technical disagreements | Community policing | Ungratefulness | No assholes allowed | Basecamp | Open source persona | Aspirations | Guarding motivations | Dealing with audiences | Pressure | Honesty | Diverse opinions | Small talk</h3> This is the text header: David Heinemeier Hansson This is the text date: Oct 18 | 2016 This is the text subtitle: Rails community | Tone | Technical disagreements | Community policing | Ungratefulness | No assholes allowed | Basecamp | Open source persona | Aspirations | Guarding motivations | Dealing with audiences | Pressure | Honesty | Diverse opinions | Small talk This is the raw header: <h2 class="post-title"><a href="episodes/141/">Zach Holman</a></h2> This is the raw date: <span class="post-date">Oct 12 | 2016</span> This is the raw subtitle: <h3 class="topic-list">Getting Fired | Taboo | Transparency | Different Perspectives | Timing | Growth Stages | Employment & Dating | Managers | At-will Employment | Tech Industry | Europe | Low hanging Fruits | Performance Improvement Plans | Meeting Goals | Surprise Firings | Firing Fast | Mistakes | Company Culture | Communication</h3> This is the text header: Zach Holman This is the text date: Oct 12 | 2016 This is the text subtitle: Getting Fired | Taboo | Transparency | Different Perspectives | Timing | Growth Stages | Employment & Dating | Managers | At-will Employment | Tech Industry | Europe | Low hanging Fruits | Performance Improvement Plans | Meeting Goals | Surprise Firings | Firing Fast | Mistakes | Company Culture | Communication This is the raw header: <h2 class="post-title"><a href="episodes/140/">Joel Glovier</a></h2> This is the raw date: <span class="post-date">Oct 10 | 2016</span> This is the raw subtitle: <h3 class="topic-list">Digital Product Design | Product Design @ GitHub | Loving Design | Order & Chaos | Drawing | Web Design | HospitalRun | Diversity | Startup Culture | Improving Lives | CURE International | Ember | Offline First | Hospital Information System | Designers & Open Source</h3> This is the text header: Joel Glovier This is the text date: Oct 10 | 2016 This is the text subtitle: Digital Product Design | Product Design @ GitHub | Loving Design | Order & Chaos | Drawing | Web Design | HospitalRun | Diversity | Startup Culture | Improving Lives | CURE International | Ember | Offline First | Hospital Information System | Designers & Open Source This is the raw header: <h2 class="post-title"><a href="episodes/139/">João Ferreira</a></h2> This is the raw date: <span class="post-date">Aug 26 | 2015</span> This is the raw subtitle: <h3 class="topic-list">Masters @ Work | Subvisual | Deadlines | Design personality | Design problems | Team | Pushing envelopes | Delightful experiences | Perfecting details | Company values</h3> This is the text header: João Ferreira This is the text date: Aug 26 | 2015 This is the text subtitle: Masters @ Work | Subvisual | Deadlines | Design personality | Design problems | Team | Pushing envelopes | Delightful experiences | Perfecting details | Company values This is the raw header: <h2 class="post-title"><a href="episodes/138/">Corwin Harrell</a></h2> This is the raw date: <span class="post-date">Aug 06 | 2015</span> This is the raw subtitle: <h3 class="topic-list">Q&A | 01 | University | Graphic design | Design setup | Sublime | Atom | thoughtbot | Working location | Collaboration & pairing | Vim advocates | Daily routine | Standups | Clients | Coffee walks | Investment Fridays |</h3> This is the text header: Corwin Harrell This is the text date: Aug 06 | 2015 This is the text subtitle: Q&A | 01 | University | Graphic design | Design setup | Sublime | Atom | thoughtbot | Working location | Collaboration & pairing | Vim advocates | Daily routine | Standups | Clients | Coffee walks | Investment Fridays | This is the raw header: <h2 class="post-title"><a href="episodes/137/">Roberto Machado</a></h2> This is the raw date: <span class="post-date">Aug 03 | 2015</span> This is the raw subtitle: <h3 class="topic-list">CEO @ Subvisual | RubyConf Portugal | Creators School | Consultancy | Company role models | Group Buddies | Portuguese startup | Rebranding | Technologies used | JS frameworks | TDD & BDD | Startup mistakes | Culture of learning | Young entrepreneurs</h3> This is the text header: Roberto Machado This is the text date: Aug 03 | 2015 This is the text subtitle: CEO @ Subvisual | RubyConf Portugal | Creators School | Consultancy | Company role models | Group Buddies | Portuguese startup | Rebranding | Technologies used | JS frameworks | TDD & BDD | Startup mistakes | Culture of learning | Young entrepreneurs This is the raw header: <h2 class="post-title"><a href="episodes/136/">James Edward Gray II</a></h2> This is the raw date: <span class="post-date">Jul 30 | 2015</span> This is the raw subtitle: <h3 class="topic-list">Screencasting | Less Code | Reading code | Getting unstuck | Rails’s codebase | CodeNewbie | Small examples | Future plans | PeepCode | Frequency & pricing</h3> This is the text header: James Edward Gray II This is the text date: Jul 30 | 2015 This is the text subtitle: Screencasting | Less Code | Reading code | Getting unstuck | Rails’s codebase | CodeNewbie | Small examples | Future plans | PeepCode | Frequency & pricing
Lúc này, chúng ta đã có dữ liệu để "chơi". Chúng ta có thể cấu trúc hoặc "xé toạc" tùy ý. Trên chỉ để hiển thị dữ liệu dễ đọc. Tất nhiên chúng ta có thể đào sâu hơn vào từng mục bằng regex với phương thức text.
Chúng ta sẽ xem chi tiết hơn khi giải quyết bài toán podcast thực tế. Không phải lớp học regex, nhưng bạn sẽ thấy thêm nó hoạt động – đừng lo, không nhiều đến mức làm não bạn chảy máu.
Attributes
Thứ có thể hữu ích ở giai đoạn này là trích xuất href cho từng tập. Nó không thể đơn giản hơn.
some_scraper.rb
require 'nokogiri'
require "open-uri"
url = 'https://betweenscreens.fm/'
page = Nokogiri::HTML(open(url))
articles = page.css("article.index-article")
articles.each do |article|
header = article.at_css("h2.post-title")
date = article.at_css(".post-date")
subtitle = article.at_css(".topic-list")
link = article.at_css("h2.post-title a")
podcast_url = "https://betweenscreens.fm/"
puts "This is the raw header: #{header}"
puts "This is the raw date: #{date}"
puts "This is the raw subtitle: #{subtitle}"
puts "This is the raw link: #{link}\n\n"
puts "This is the text header: #{header.text}"
puts "This is the text date: #{date.text}"
puts "This is the text subtitle: #{subtitle.text}"
puts "This is the raw link: #{podcast_url}#{link[:href]}\n\n"
end
Những điểm quan trọng nhất cần chú ý ở đây là [:href] và podcast_url. Nếu bạn gắn [:], bạn có thể trích xuất attribute từ selector nhắm mục tiêu. Tôi trừu tượng hóa thêm chút, nhưng bạn thấy rõ hơn cách hoạt động bên dưới.
... href = article.at_css("h2.post-title a")[:href] ...
Để có URL hoàn chỉnh và hữu dụng, tôi lưu domain gốc vào biến và xây dựng URL đầy đủ cho mỗi tập.
... podcast_url = "https://betweenscreens.fm/"
puts "This is the raw link: #{podcast_url}#{link[:href]}\n\n" ...
Hãy nhìn nhanh đầu ra:
Đầu ra
This is the raw header: <h2 class="post-title"><a href="episodes/143/">Jason Long</a></h2> This is the raw date: <span class="post-date">Oct 25 | 2016</span> This is the raw subtitle: <h3 class="topic-list">Open source | Empathy | Lower barriers | Learning tool | Design contributions | Git website | Branding | GitHub | Neovim | Tmux | Design love | Knowing audiences | Showing work | Dribbble | Progressions | Ideas</h3> This is the raw link: <a href="episodes/143/">Jason Long</a> This is the text header: Jason Long This is the text date: Oct 25 | 2016 This is the text subtitle: Open source | Empathy | Lower barriers | Learning tool | Design contributions | Git website | Branding | GitHub | Neovim | Tmux | Design love | Knowing audiences | Showing work | Dribbble | Progressions | Ideas This is the href: https://betweenscreens.fm/episodes/143/ This is the raw header: <h2 class="post-title"><a href="episodes/142/">David Heinemeier Hansson</a></h2> This is the raw date: <span class="post-date">Oct 18 | 2016</span> This is the raw subtitle: <h3 class="topic-list">Rails community | Tone | Technical disagreements | Community policing | Ungratefulness | No assholes allowed | Basecamp | Open source persona | Aspirations | Guarding motivations | Dealing with audiences | Pressure | Honesty | Diverse opinions | Small talk</h3> This is the raw link: <a href="episodes/142/">David Heinemeier Hansson</a> This is the text header: David Heinemeier Hansson This is the text date: Oct 18 | 2016 This is the text subtitle: Rails community | Tone | Technical disagreements | Community policing | Ungratefulness | No assholes allowed | Basecamp | Open source persona | Aspirations | Guarding motivations | Dealing with audiences | Pressure | Honesty | Diverse opinions | Small talk This is the href: https://betweenscreens.fm/episodes/142/ This is the raw header: <h2 class="post-title"><a href="episodes/141/">Zach Holman</a></h2> This is the raw date: <span class="post-date">Oct 12 | 2016</span> This is the raw subtitle: <h3 class="topic-list">Getting Fired | Taboo | Transparency | Different Perspectives | Timing | Growth Stages | Employment & Dating | Managers | At-will Employment | Tech Industry | Europe | Low hanging Fruits | Performance Improvement Plans | Meeting Goals | Surprise Firings | Firing Fast | Mistakes | Company Culture | Communication</h3> This is the raw link: <a href="episodes/141/">Zach Holman</a> This is the text header: Zach Holman This is the text date: Oct 12 | 2016 This is the text subtitle: Getting Fired | Taboo | Transparency | Different Perspectives | Timing | Growth Stages | Employment & Dating | Managers | At-will Employment | Tech Industry | Europe | Low hanging Fruits | Performance Improvement Plans | Meeting Goals | Surprise Firings | Firing Fast | Mistakes | Company Culture | Communication This is the href: https://betweenscreens.fm/episodes/141/ This is the raw header: <h2 class="post-title"><a href="episodes/140/">Joel Glovier</a></h2> This is the raw date: <span class="post-date">Oct 10 | 2016</span> This is the raw subtitle: <h3 class="topic-list">Digital Product Design | Product Design @ GitHub | Loving Design | Order & Chaos | Drawing | Web Design | HospitalRun | Diversity | Startup Culture | Improving Lives | CURE International | Ember | Offline First | Hospital Information System | Designers & Open Source</h3> This is the raw link: <a href="episodes/140/">Joel Glovier</a> This is the text header: Joel Glovier This is the text date: Oct 10 | 2016 This is the text subtitle: Digital Product Design | Product Design @ GitHub | Loving Design | Order & Chaos | Drawing | Web Design | HospitalRun | Diversity | Startup Culture | Improving Lives | CURE International | Ember | Offline First | Hospital Information System | Designers & Open Source This is the href: https://betweenscreens.fm/episodes/140/ This is the raw header: <h2 class="post-title"><a href="episodes/139/">João Ferreira</a></h2> This is the raw date: <span class="post-date">Aug 26 | 2015</span> This is the raw subtitle: <h3 class="topic-list">Masters @ Work | Subvisual | Deadlines | Design personality | Design problems | Team | Pushing envelopes | Delightful experiences | Perfecting details | Company values</h3> This is the raw link: <a href="episodes/139/">João Ferreira</a> This is the text header: João Ferreira This is the text date: Aug 26 | 2015 This is the text subtitle: Masters @ Work | Subvisual | Deadlines | Design personality | Design problems | Team | Pushing envelopes | Delightful experiences | Perfecting details | Company values This is the href: https://betweenscreens.fm/episodes/139/ This is the raw header: <h2 class="post-title"><a href="episodes/138/">Corwin Harrell</a></h2> This is the raw date: <span class="post-date">Aug 06 | 2015</span> This is the raw subtitle: <h3 class="topic-list">Q&A | 01 | University | Graphic design | Design setup | Sublime | Atom | thoughtbot | Working location | Collaboration & pairing | Vim advocates | Daily routine | Standups | Clients | Coffee walks | Investment Fridays |</h3> This is the raw link: <a href="episodes/138/">Corwin Harrell</a> This is the text header: Corwin Harrell This is the text date: Aug 06 | 2015 This is the text subtitle: Q&A | 01 | University | Graphic design | Design setup | Sublime | Atom | thoughtbot | Working location | Collaboration & pairing | Vim advocates | Daily routine | Standups | Clients | Coffee walks | Investment Fridays | This is the href: https://betweenscreens.fm/episodes/138/ This is the raw header: <h2 class="post-title"><a href="episodes/137/">Roberto Machado</a></h2> This is the raw date: <span class="post-date">Aug 03 | 2015</span> This is the raw subtitle: <h3 class="topic-list">CEO @ Subvisual | RubyConf Portugal | Creators School | Consultancy | Company role models | Group Buddies | Portuguese startup | Rebranding | Technologies used | JS frameworks | TDD & BDD | Startup mistakes | Culture of learning | Young entrepreneurs</h3> This is the raw link: <a href="episodes/137/">Roberto Machado</a> This is the text header: Roberto Machado This is the text date: Aug 03 | 2015 This is the text subtitle: CEO @ Subvisual | RubyConf Portugal | Creators School | Consultancy | Company role models | Group Buddies | Portuguese startup | Rebranding | Technologies used | JS frameworks | TDD & BDD | Startup mistakes | Culture of learning | Young entrepreneurs This is the href: https://betweenscreens.fm/episodes/137/
Gọn gàng, phải không? Bạn có thể làm tương tự để trích xuất [:class] của selector.
require 'nokogiri'
require "open-uri"
url = 'https://betweenscreens.fm/'
page = Nokogiri::HTML(open(url))
body_classes = page.at_css("body")[:class]
Nếu node đó có nhiều class, bạn sẽ nhận được danh sách tất cả chúng.
Điều hướng Node
- parent
- children
- previous_sibling
- next_sibling
Chúng ta quen làm việc với cấu trúc cây trong CSS hoặc thậm chí jQuery. Sẽ đau khổ nếu Nokogiri không cung cấp API tiện lợi để di chuyển trong cây như vậy.
some_scraper.rb
require 'nokogiri'
require "open-uri"
url = 'https://betweenscreens.fm/'
page = Nokogiri::HTML(open(url))
header = page.at_css("h2.post-title")
header_children = page.at_css("h2.post-title").children
header_parent = page.at_css("h2.post-title").parent
header_prev_sibling = page.at_css("h2.post-title").previous_sibling
puts "#{header}\n\n"
puts "#{header_children}\n\n"
puts "#{header_parent}\n\n"
puts "#{header_prev_sibling}\n\n"
Đầu ra
#header <h2 class="post-title"><a href="episodes/143/">Jason Long</a></h2> #header_children <a href="episodes/143/">Jason Long</a> #header_parent <article class="index-article"> <span class="post-date">Oct 25 | 2016</span><h2 class="post-title"><a href="episodes/143/">Jason Long</a></h2> <h3 class="topic-list">Open source | Empathy | Lower barriers | Learning tool | Design contributions | Git website | Branding | GitHub | Neovim | Tmux | Design love | Knowing audiences | Showing work | Dribbble | Progressions | Ideas</h3> <div class="soundcloud-player-small"> </div> </article> #header_previous_sibling <span class="post-date">Oct 25 | 2016</span>
Như bạn tự thấy, đây là thứ khá mạnh – đặc biệt khi thấy .parent thu thập được nhiều thứ trong một lần. Thay vì định nghĩa nhiều node thủ công, bạn có thể thu thập hàng loạt.
Bạn thậm chí có thể chain chúng cho duyệt phức tạp hơn. Bạn có thể làm phức tạp tùy thích, nhưng tôi khuyên giữ mọi thứ đơn giản. Nó nhanh chóng trở nên cồng kềnh và khó hiểu. Nhớ: "Keep it simple, stupid!"
... header_parent_parent = page.at_css("h2.post-title").parent.parent
header_prev_sibling_parent_children = page.at_css("h2.post-title").previous_sibling.parent.children ...
some_scraper.rb
require 'nokogiri'
require "open-uri"
url = 'https://betweenscreens.fm/'
page = Nokogiri::HTML(open(url))
header = page.at_css("h2.post-title")
header_prev_sibling_children = page.at_css("h2.post-title").previous_sibling.children
header_parent_parent = page.at_css("h2.post-title").parent.parent
header_prev_sibling_parent = page.at_css("h2.post-title").previous_sibling.parent
header_prev_sibling_parent_children = page.at_css("h2.post-title").previous_sibling.parent.children
puts "#{header}\n\n"
puts "#{header_prev_sibling_children}\n\n"
puts "#{header_parent_parent}\n\n"
puts "#{header_prev_sibling_parent}\n\n"
puts "#{header_prev_sibling_parent_children}\n\n"
Đầu ra
#header <h2 class="post-title"><a href="episodes/143/">Jason Long</a></h2> #header_previous_sibling_children Oct 25 | 2016 #header_parent_parent <li> <article class="index-article"> <span class="post-date">Oct 25 | 2016</span><h2 class="post-title"><a href="episodes/143/">Jason Long</a></h2> <h3 class="topic-list">Open source | Empathy | Lower barriers | Learning tool | Design contributions | Git website | Branding | GitHub | Neovim | Tmux | Design love | Knowing audiences | Showing work | Dribbble | Progressions | Ideas</h3> <div class="soundcloud-player-small"> </div> </article> </li> #header_previous_sibling_parent <article class="index-article"> <span class="post-date">Oct 25 | 2016</span><h2 class="post-title"><a href="episodes/143/">Jason Long</a></h2> <h3 class="topic-list">Open source | Empathy | Lower barriers | Learning tool | Design contributions | Git website | Branding | GitHub | Neovim | Tmux | Design love | Knowing audiences | Showing work | Dribbble | Progressions | Ideas</h3> <div class="soundcloud-player-small"> </div> </article> #header_previous_sibling_parent_children <span class="post-date">Oct 25 | 2016</span><h2 class="post-title"><a href="episodes/143/">Jason Long</a></h2> <h3 class="topic-list">Open source | Empathy | Lower barriers | Learning tool | Design contributions | Git website | Branding | GitHub | Neovim | Tmux | Design love | Knowing audiences | Showing work | Dribbble | Progressions | Ideas</h3> <div class="soundcloud-player-small"> </div>
Lời kết
Nokogiri không phải thư viện khổng lồ, nhưng nó cung cấp nhiều thứ. Tôi khuyên bạn tự thực hành những gì đã học và mở rộng kiến thức qua tài liệu khi gặp khó khăn. Nhưng đừng tự rước rắc!
Phần giới thiệu nhỏ này sẽ đưa bạn lên đường hiểu được bạn có thể làm gì và nó hoạt động như thế nào. Tôi hy vọng bạn sẽ tự khám phá thêm và vui vẻ với nó. Như bạn sẽ tự khám phá, đây là công cụ phong phú và không ngừng cho.