Hướng dẫn web scraping bằng Cheerio

Nếu một trang web không cung cấp API tốt, bạn nên dùng web scraping để thu thập thông tin của nó. Cheerio và Express.js sẽ giúp bạn làm việc này.

Web Scraping dữ liệu web

Web scraping là một kỹ thuật khiến nó có thể đạt được dữ liệu từ một web cụ thể. Trang web dùng HTML để mô tả nội dung. Nếu HTML gọn gàng và rõ ngữ nghĩa, thì thật dễ dùng nó để xác định dữ liệu hữu ích.

Bạn thường dùng web scraper để thu thập và giám sát dữ liệu, đồng thời kiểm tra thay đổi trong tương lai.

Trước khi dùng Cheerio, bạn có cần biết các khái niệm jQuery?

jQuery là một trong số package JavaScript phổ biến nhất hiện tại. Nó giúp bạn dễ dàng làm việc với DOM, xử lý sự kiện, hoạt ảnh và nhiều hơn thế nữa. Cheerio là một package cho web scraping, được xây dựng dựa trên jQuery - chia sẻ cùng cú pháp và API, đồng thời khiến nó dễ dàng phân tích tài liệu HTML, XML.

Trước khi học cách dùng Cheerio, điều quan trọng bạn cần biết cách chọn các thành phần HTML với jQuery. Thật may, jQuery hỗ trợ hầu hết bộ chọn CSS3, giúp bạn lấy các phần tử DOM dễ dàng hơn. Giờ hãy nhìn code sau:

$("#container");

Trong khối code trên, jQuery chọn thành phầm chứa id của “container”. Việc triển khai tương tự bằng JavaScript cũ trông sẽ như sau:

document.querySelectorAll("#container");

So sánh hai khối code trước đó, bạn có thể thấy khối code trước dễ đọc hơn khối code sau. Đó là nhờ jQuery.

jQuery cũng có các phương thức hữu ích như text(), html()… để khiến nó có thể chỉnh sửa thành phần HTML. Hiện có một số phương thức bạn có thể sử dụng để duyệt qua DOM như parent(), siblings(), prev(), và next().

Phương thức each() của jQuery rất phổ biến trong nhiều dự án Cheerio. Nó cho phép bạn lặp qua các đối tượng và mảng. Cú pháp cho phương thức each() trông như sau:

$(<element>).each(<array or object>, callback)

Ở khối code trên, callback chạy cho từng lần lặp của mảng hoặc đối số đối tượng.

Tải HTML với Cheerio

Để bắt đầu phân tích dữ liệu HTML hoặc XML bằng Cheerio, bạn có thể dùng phương thức cheerio.load(). Hãy xét ví dụ sau:

const $ = cheerio.load('<html><body><h1>Hello, world!</h1></body></html>');
console.log($('h1').text())

Khối code này dùng phương thức text() của jQuery truy xuất nội dung text trong thẻ h1. Cú pháp đầy đủ cho hàm load() nhu sau:

load(content, options, mode)

Tham số content chỉ dữ liệu HTML hoặc XML bạn truyền vào phương thức load(). options là một đối tượng tùy chọn mà bạn có thể chỉnh sửa hành vi của phương thức. Mặc định, hàm load() giới thiệu các phần html, headbody nếu chúng bị thiếu. Nếu muốn dừng hành vi này, đảm bảo bạn đặt mode sang false.

Thu thập tin tức trên trang Hacker News bằng Cheerio

Giờ là lúc bạn kết hợp mọi thứ đã học để tạo một web scraper đơn giản. Hacker News là một web phổ biến cho doanh nghiệp và nhà sáng tạo. Nó cũng là web hoàn hảo để khai thác kỹ năng tìm kiếm trên web vì nó tải nhanh, có giao diện vô cùng đơn giản và không chứa quảng cáo.

Đảm bảo bạn đang chạy Node.js và Node Package Manager trên máy tính. Tạo thư mục trống, sau đó, tới file package.json và thêm JSON sau vào bên trong file:

{
  "name": "web-scraper",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon index.js"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "cheerio": "^1.0.0-rc.12",
    "express": "^4.18.2"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}

Sau đó, mở terminal và chạy:

npm i

Hành động này sẽ cài các phần phụ thuộc cần thiết để xây dựng scraper. Nhưng package này bao gồm Cheerio để phân tích HTML, ExpressJS để tạo server, và phần phụ thuộc phát triển - Nodemon, một tiện ích lắng nghe thay đổi trong dự án và tự động khởi động lại server.

Thiết lập mọi thứ và tạo các hàm cần thiết

Tạo file index.js và trong file đó, tạo một biến cố định tên “PORT”. Đặt PORT sang 5500 (hoặc bất kỳ số bạn chọn), sau đó, nhập các package CheerioExpress tương ứng.

const PORT = 5500;
const cheerio = require("cheerio");
const express = require("express");
const app = express();

Tiếp theo, xác định 3 biến: url, html, và finishedPage. Đặt url sang URL Hacker News.

const url = 'https://news.ycombinator.com';
let html;
let finishedPage;

Giờ tạo một hàm tên getHeader() trả về một số HTML mà trình duyệt này sẽ hiển thị.

function getHeader(){
    return `
        <div style="display:flex; flex-direction:column; align-items:center;">
        <h1 style="text-transform:capitalize">Scraper News</h1>
        <div style="display:flex; gap:10px; align-items:center;">
        <a href="/" id="news" onClick='showLoading()'>Home</a>
        <a href="/best" id="best" onClick='showLoading()'>Best</a>
        <a href="/newest" id="newest" onClick='showLoading()'>Newest</a>
        <a href="/ask" id="ask" onClick='showLoading()'>Ask</a>
        <a href="/jobs" id="jobs" onClick='showLoading()'>Jobs</a>
        </div>
        <p class="loading" style="display:none;">Loading...</p>
        </div>
`}

Hàm khác tạo getScript() trả về một số JavaScript cho trình duyệt để chạy. Đảm bảo bạn chuyển trong biến type là đối số khi gọi nó.

function getScript(type){
    return `
    <script>
    document.title = "${type.substring(1)}"

    window.addEventListener("DOMContentLoaded", (e) => {
      let navLinks = [...document.querySelectorAll("a")];
      let current = document.querySelector("#${type.substring(1)}");
      document.body.style = "margin:0 auto; max-width:600px;";
      navLinks.forEach(x => x.style = "color:black; text-decoration:none;");
      current.style.textDecoration = "underline";
      current.style.color = "black";
      current.style.padding = "3px";
      current.style.pointerEvents = "none";
    })

    function showLoading(e){
      document.querySelector(".loading").style.display = "block";
      document.querySelector(".loading").style.textAlign = "center";
    }
    </script>`
}

Cuối cùng, tạo hàm không đồng bộ tên fetchAndRenderPage(). Chức năng này hoạt động chính xác như tên gọi. Nó thu thập dữ liệu từ trang Hacker News, phân tích và định dạng nó bằng Cheerio, sau đó, gửi một số HTML lại client để hiển thị.

async function fetchAndRenderPage(type, res) {
    const response = await fetch(`${url}${type}`)
    html = await response.text();
}

Hacker News có 3 kiểu bài đăng khác nhau. “news” là nội dung ở trang trước. “best” là nội dung bài viết đang tạo xu hướng. “newest” là nhãn cho bài viết mới nhất.

fetchAndRenderPage() tìm nạp danh sách bài viết trên trang Hacker News dựa trên dữ liệu bạn chuyển làm đối số. Nếu hoạt động tìm nạp thành công, hàm này liên kết biến sang text phản hồi.

Tiếp theo, thêm các dòng sau vào hàm này:

res.set('Content-Type', 'text/html');
res.write(getHeader());

const $ = cheerio.load(html);
const articles = [];
let i = 1;

Trong khối code trên, phương thức set() đặt trường header HTTP. Write() chịu trách nhiệm gửi một đoạn nội dung phản hồi. Hàm load() gửi một đoạn nội dung phản hồi. Hàm load() lấy html làm đối số.

Tiếp theo, thêm các dòng sau để chọn thành phần con tương ứng của tất cả các phần tử với class “titleline”.

$('.titleline').children('a').each(function(){
    let title = $(this).text();
    articles.push(`<h4>${i}. ${title}</h4>`);
    i++;
})

Trong khối code này, mỗi lần lặp sẽ truy xuất nội dung text của thành phần HTML mục tiêu và chứa nó trong biến title.

Tiếp theo, đẩy phản hồi từ hàm getScript() vào mảng articles. Sau đó, tạo biến finishedPage chứa HTML đã hoàn thiện để gửi sang trình duyệt đó. Cuối cùng, dùng phương thức write() để gửi finishedPage dưới dạng một đoạn và kết thúc quá trình phản hồi bằng hàm end().

articles.push(getScript(type))
finishedPage = articles.reduce((c, n) => c + n);
res.write(finishedPage);
res.end();

Trên đây là cách web scraping bằng Cheerio. Hi vọng bài viết hữu ích với các bạn.

Thứ Hai, 07/08/2023 09:50
51 👨 321
0 Bình luận
Sắp xếp theo