Static Web Craping với NodeJS và Cheerio


Chắc hẳn từ khóa này các bạn đã được nghe đến ở đâu đó. Vậy, nó là gì ? Trong bài viết này mình sẽ giới thiệu với các bạn một kĩ thuật khá là hay ho và được giới Developer sử dụng rất nhiều chứ không riêng gì các bạn viết Web.





Web Scraping là kĩ thuật thu thập thông tin của một Website bằng việc request đến url của trang cần lấy dữ liệu và download file html DOM của url đó.





Trong Web Scraping chúng ta đã phần nào sử dụng đến Web Crawler. Vậy 2 thằng này khác nhau như thế nào ? Hai khái niệm này có sự liên quan với nhau. Web Scraping là quá trình thu thập thông tin từ một url cho trước. Còn Web Crawler sẽ download toàn bộ nội dung của trang đồng thời tìm tất cả các liên kết trong trang đó và tiếp tục download chúng, khi thao tác tải hoàn tất data sẽ được đánh chỉ số index rồi add on database. Thông thường ta sử dụng kĩ thuật Scraping để thu một số dữ liệu cần thiết của trang chứ không lấy hết toàn bộ.









Một số việc mà ta có thể áp dụng kĩ thuật này là: lấy data như giá, hình ảnh, comment, ... về sản phẩm của các trang thương mại điện tử và sử dụng với mục đích cá nhân như là so sánh giá của sản phẩm ở các trang thương mại khác hay giá sản phẩm hiện tại và của quá khứ xem chênh lệch như thế nào , ... Ngoài ra cũng còn khá nhiều ứng dụng hay ho về kĩ thuật này, các bạn tự mình khám phá nhé.








* Yêu cầu:





Máy của bạn phải cài môi trường Node 8.0 và phải có npm. Nếu đang dùng phiên bản Node thấp hơn thì bạn hãy tham khảo cách để Update nhé.









Khởi đầu dự án





Các bạn new một thư mới và open code editer tại đó nhé. Tiếp theo các bạn initialize một dự án npm với câu lệnh:





npm init





Sau bước này các bạn sẽ thấy một file có tên là package.json vừa được tạo ra. Sau đó ta cần cài các package sau: request, cheerio, axios bằng câu lệnh:





npm i request cheerio axios dotenv --save




  • package request: dùng để tạo 1 request đến url cần scraping
  • package cheerio: dùng để parser respond của request trả về dưới dạng cấu trúc cây DOM
  • package axios: dùng để gửi request post data lên lưu ở database
  • package dotenv: dùng để cài đặt các biến môi trường








Request URL





Mình sẽ nói sơ qua về cấu trúc thư mục của dự án một tí





Do ở đâu mình sử dụng Editor Visual Studio Code nên khi làm việc nó tạo 1 file .vscode như thế




  • File .env dùng để thiết lập các biến môi trường, các Private key mà các bạn không muốn để người khác biết. Nhớ thêm nó vào File .gitignore để tránh commit nhầm nhé !
  • File scraping.js là file code chính của chúng ta.
  • File package.json và package-lock.json thì quá quen thuộc với các bạn rồi nên mình không nói đến nha.
  • File .gitignore xuất hiện là do mình có git init tại project để sau này deploy lên GIT các bạn có thể tham khảo.






Đầu tiên, file .env có gì:









File này có Format dạng key=value. Ở đây khi biên dịch thì nó trở thành chuỗi cả nên mình không cần để nó trong nháy đơn hay kép gì hết, nếu có nhiều cặp giá trị thì các bạn cứ phẩy rồi viết tiếp





Vậy thì cái value của key ở trên do đâu mà ra thì mình sẽ hướng dẫn các bạn sử dụng thằng mockapi làm server giả để post dữ liệu mình scraping được lưu trữ lên đó nhé. Còn các bạn nếu có Database rùi thì cứ post lên server của các bạn.





Đầu tiên bạn vào trang https://www.mockapi.io và reg cho mình một cái tài khoản. Sau khi các bạn login, tại giao diện chính các bạn new cho mình một cái project và đặt tên gì cũng được.









Giả sử mình tạo như thế. Sau khi tạo xong các bạn chọn Project vừa rồi và New Resource. Ở mục resource name là một endpoint của API, các bạn cũng đặt tên bình thường.
Phần quan trong đây: ở mục schema biểu diễn các trường dữ liệu mà các bạn cần lưu. Ở đây mình sẽ tạo như thế này:









Yeah, Click create nữa là xong.







Bài viết này mình sẽ Demo cách Scraping những bài báo về corona ở trang:
https://thanhnien.vn/tin-tuc/covid-19.html





Đầu tiên ta send 1 request lên url này và lấy html về:





require('dotenv').config();

var request = require('request');

request('https://thanhnien.vn/tin-tuc/covid-19.html', (error, response, html) => {
if (!error && response.statusCode == 200) {
// làm gì đó ở đây
} else {
console.log(error);
}
});




Mục đích của dòng 1 là nhúng cặp key=value vừa rồi ta khai báo ở File .env vào môi trường node. Các bạn có thể kiểm tra xem nó có chưa bằng cách log nó ra xem thử: console.log(process.env."key")





Function request nhận vào 2 tham số là url và function. Ở đây mình viết dưới dạng arrow function. Arrow Func này có 3 giá trị:





  • error: trả về lỗi nếu request đến url không thành
  • respond: trả về respond từ phía url
  • html: html của url đó nếu thành công








Cheerio Load









require('dotenv').config();

var request = require('request');
var cheerio = require('cheerio');

request('https://thanhnien.vn/tin-tuc/covid-19.html', (error, response, html) => {
if (!error && response.statusCode == 200) {
const $ = cheerio.load(html);

$('.relative').find('.story').each((index, story) => {
if (story) {
var image = $(story).find('a').find('img').attr('data-src');
var link = $(story).find('a').attr('href');
var header = $(story).find('h2').find('.story__title').text();
var meta = $(story).find('.meta').find('.timebox').text();
var summary = $(story).find('.summary').find('div').text();

apiAddArticle(api_secret, {
image,
link,
header,
meta,
summary
});
}
})
} else {
console.log(error);
}
});




Sau khi có html ta có nhiều cách để trích xuất những data mà chúng ta muốn. Ở đây mình dùng thằng cheerio để load html đó vào một biến (dòng 8) và sử dụng jQuery selecter để bóc tách. Các bạn cũng có thể dùng selecter của window như document.querySelecterAll, ...





Trước tiên ta hãy xem cấu trúc của trang báo thanh niên đó như thế nào nhé !









Ta thấy, một div có class relative bao tất cả các div có class story mà các div.story này có thể xem như 1 component data mà ta có thể trích xuất. Ví dụ với component đầu tiên thì các dữ liệu ta có thể trích xuất là: thumbnail, header, meta (1 tiếng trước), và một đoạn summary, có thể là số comment nữa.





Với dòng selecter: $('.relative').find('.story') ta sẽ nhận được một mảng các component có class là story nằm trong div.relative. Tiếp theo ta cùng xem bên trong có gì nữa nhé !









Với các thẻ và thuộc tính mà mình hightlight thì các bạn cũng có thể selecter ra được các field mà mình muốn. Các selecter ở dòng 12 đến 16 các bạn có thể xem cấu trúc cây sẽ hiểu hơn nhé.









Request Post to Database









require('dotenv').config();

var request = require('request');
var cheerio = require('cheerio');
var axios = require('axios');

var api_secret = process.env.API_SECRET;

function apiAddArticle(api_secret, data){
axios.post(api_secret, data)
.then(function () {
console.log("Add successed");
})
.catch(function () {
console.log("Add failed");
});
}




Ở mục trên mình có gọi 1 function apiAddArticle() là để gửi 1 request post yêu cầu database add bài báo. Ở đây mình dùng thằng Axios để call API, các bạn có thể cùng thằng nào tùy thích nhé.





Nhớ lấy api_secret ở biến môi trường ra nhé !









Code hoàn chỉnh:









require('dotenv').config();

var request = require('request');
var cheerio = require('cheerio');
var axios = require('axios');

var api_secret = process.env.API_SECRET;

function apiAddArticle(api_secret, data){
axios.post(api_secret, data)
.then(function () {
console.log("Add successed");
})
.catch(function () {
console.log("Add failed");
});
}

request('https://thanhnien.vn/tin-tuc/covid-19.html', (error, response, html) => {
if (!error && response.statusCode == 200) {
const $ = cheerio.load(html);

$('.relative').find('.story').each((index, story) => {
if (story) {
var image = $(story).find('a').find('img').attr('data-src');
var link = $(story).find('a').attr('href');
var header = $(story).find('h2').find('.story__title').text();
var meta = $(story).find('.meta').find('.timebox').text();
var summary = $(story).find('.summary').find('div').text();

apiAddArticle(api_secret, {
image,
link,
header,
meta,
summary
});
}
})
} else {
console.log(error);
}
});








Và đây là kết quả trên server của mình:














Github project: https://github.com/Nguyen-Quoc-Thai/web-scraping/tree/master









Summary





Cherrio sử dụng cấu trúc cây DOM parser rất thân thiện khi sử dụng, nó đặt biệt nhanh và hiệu quả với những trang text nhưng dữ liệu có thể bị thiếu nếu bạn truy cập một trang web phức tạp có nhiều tính năng sử dụng cookie hoặc là các trang web sử dụng AJAX để tương tác với người dùng. Lúc này bạn có thể nghĩ tới việc dùng puppeteer thay vì cheerio mà bài viết trước team mình đã đề cập.






Chúc các bạn thành công !





Writer: Quốc Thái





Follow me via:
Facebook
Github







Nhận xét