Bạn đã bao giờ cần lấy một danh sách sản phẩm, tin tức, hay bất kỳ dữ liệu nào từ một trang web nhưng lại phải copy-paste thủ công hàng giờ liền? Nếu câu trả lời là có, thì bài viết này chính là dành cho bạn.
Hôm nay, Digotech sẽ hướng dẫn bạn một phương pháp cào dữ liệu (web scraping) vô cùng mạnh mẽ mà không cần cài đặt bất kỳ phần mềm phức tạp nào. Chúng ta sẽ sử dụng chính công cụ có sẵn trong trình duyệt (DevTools) và một vài đoạn mã JavaScript “thần thánh” để tự động hóa toàn bộ quá trình.
Hãy cùng bắt đầu hành trình trở thành một “thợ mỏ” dữ liệu chuyên nghiệp nhé!
Bước 1: Khảo sát “hiện trường” – Phân tích website mục tiêu
Trước khi viết bất kỳ dòng code nào, việc đầu tiên và quan trọng nhất là phải “do thám” trang web mà chúng ta muốn lấy dữ liệu.
Mở trang web bạn muốn cào (trong ví dụ này, chúng ta sử dụng một trang tin tức thực tế) và nhấn phím F12 để mở công cụ cho nhà phát triển (DevTools). Chuyển qua tab Elements.
Bây giờ, hãy dùng công cụ Inspect Element (biểu tượng con trỏ chuột ở góc trái DevTools) và di chuột vào các thành phần bạn muốn lấy để trả lời các câu hỏi sau:
- Dữ liệu cần lấy nằm ở đâu?
- Tiêu đề bài viết có class hay ID là gì?
- Nội dung nằm trong thẻ nào?
- Link ảnh đại diện có cấu trúc ra sao?
- Làm sao để vào trang chi tiết?
- Link để vào xem chi tiết bài viết là link nào? Nó nằm ở tiêu đề hay ở nút “Xem thêm”? Selector của nó là gì? (Đây là một bước cực kỳ quan trọng!)
- Cấu trúc phân trang như thế nào?
- Cuộn xuống cuối trang, xem các nút chuyển trang (1, 2, 3…).
- Nút “Next” (hay “Sau”, “Tiếp”,
»
) có class hay cấu trúc HTML như thế nào? Đây là chìa khóa để tự động hóa việc lướt qua tất cả các trang.
Sau bước này, chúng ta đã có trong tay “bản đồ” của trang web.
Bước 2: Xây dựng “Bộ xương” Script
Chúng ta sẽ tiếp cận bài toán bằng cách xây dựng 2 hàm JavaScript chính:
getArticleData(url)
: Một hàm chuyên đi lấy dữ liệu (tiêu đề, nội dung, ảnh) từ một trang chi tiết cụ thể.scrapeAllPagesAndArticles()
: Hàm tổng chỉ huy, có nhiệm vụ lướt qua tất cả các trang, thu thập link và sau đó gọi hàmgetArticleData
để xử lý từng link một.
Bước 3: Dạy Script cách lấy dữ liệu chi tiết
Đây là lúc chúng ta sử dụng những thông tin đã do thám ở Bước 1. Hàm getArticleData
sẽ dùng lệnh fetch
để lấy nội dung HTML của một trang, sau đó “bóc tách” dữ liệu bằng querySelector
với các “Selector” chúng ta đã xác định.
// Hàm này để lấy dữ liệu chi tiết từ một trang bài viết
async function getArticleData(url) {
try {
const response = await fetch(url);
const text = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
// Sử dụng các selector chính xác đã tìm được ở Bước 1
const title = doc.querySelector('h1.detail-title')?.innerText.trim();
const content = doc.querySelector('div.divcontent')?.innerHTML;
const imageUrl = doc.querySelector('div.divcontent img')?.src;
if (!title) return null; // Bỏ qua nếu không lấy được tiêu đề
return {
tieu_de: title,
noi_dung: content,
anh_thumbnail: imageUrl,
link_goc: url
};
} catch (error) {
console.error(`Lỗi khi lấy dữ liệu từ: ${url}`, error);
return null;
}
}
Giải thích các “Selector”
Có thể bạn sẽ thắc mắc những đoạn mã như 'h1.detail-title'
là gì. Đây được gọi là CSS Selector, một “địa chỉ” để chỉ cho script biết chính xác phải tìm đến phần tử nào trên trang web.
'h1.detail-title'
:h1
: Tìm thẻ<h1>
(thường là tiêu đề chính, lớn nhất của trang)..detail-title
: Nhưng không phải thẻ<h1>
nào cũng được, chỉ lấy thẻ<h1>
nào có class làdetail-title
. (Dấu.
đại diện cho class).
'div.divcontent'
:div
: Tìm một thẻ<div>
(thẻ dùng để gom nhóm nội dung)..divcontent
: Cụ thể là thẻ<div>
có class tên làdivcontent
.
'div.divcontent img'
:- Đây là một selector kết hợp.
div.divcontent
: Đầu tiên, tìm đến khối nội dung có class làdivcontent
.img
: Sau đó, tìm thẻ<img>
(thẻ hình ảnh) đầu tiên nằm bên trong khối nội dung đó.
Bằng cách chỉ định các “địa chỉ” này, chúng ta đảm bảo script lấy đúng và đủ thông tin mình cần.
Bước 4: Tự động hóa việc chuyển trang
Đây là phần “ảo diệu” nhất. Chúng ta sẽ viết một vòng lặp while
để liên tục “đọc” trang hiện tại và tìm nút “Sau”. Nếu tìm thấy, nó sẽ lấy URL của trang tiếp theo và lặp lại quá trình. Nếu không tìm thấy nút “Sau” nữa, nó hiểu rằng đã đến trang cuối cùng và dừng lại.
Logic này nằm trong hàm scrapeAllPagesAndArticles()
:
// ... bên trong hàm scrapeAllPagesAndArticles
let currentPageUrl = window.location.href;
let hasNextPage = true;
while (hasNextPage) {
console.log(`-- Đang quét trang: ${currentPageUrl}`);
const response = await fetch(currentPageUrl);
const text = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
// Lấy các link bài viết trên trang hiện tại
const linksOnPage = Array.from(doc.querySelectorAll(".left-news .title")).map(el => el.href);
allArticleUrls.push(...linksOnPage);
// Tìm chính xác nút "Sau" dựa trên phân tích ở Bước 1
const nextButton = Array.from(doc.querySelectorAll('form[name="theForm"] a')).find(a => a.innerText.trim() === 'Sau');
if (nextButton && nextButton.href) {
currentPageUrl = nextButton.href; // Cập nhật URL cho vòng lặp tiếp theo
} else {
hasNextPage = false; // Không tìm thấy, dừng vòng lặp
}
}
//...
Bước 5: Tổng hợp và Chạy Script
Bây giờ, chúng ta sẽ kết hợp tất cả lại thành một đoạn mã hoàn chỉnh. Việc của bạn chỉ là:
- Mở trang web bạn muốn cào (ví dụ: trang tin tức, trang danh sách sản phẩm).
- Nhấn F12 và mở tab Console.
- Sao chép và dán toàn bộ đoạn mã dưới đây vào Console, sau đó nhấn Enter.
// === MÃ LỆNH HOÀN CHỈNH ===
// Hàm này để lấy dữ liệu chi tiết từ một trang bài viết
async function getArticleData(url) {
try {
const response = await fetch(url);
const text = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
// Sử dụng các selector chính xác đã tìm được ở Bước 1
const title = doc.querySelector('h1.detail-title')?.innerText.trim();
const content = doc.querySelector('div.divcontent')?.innerHTML;
const imageUrl = doc.querySelector('div.divcontent img')?.src;
if (!title) return null; // Bỏ qua nếu không lấy được tiêu đề
return {
tieu_de: title,
noi_dung: content,
anh_thumbnail: imageUrl,
link_goc: url
};
} catch (error) {
console.error(`Lỗi khi lấy dữ liệu từ: ${url}`, error);
return null;
}
}
// Hàm chính để chạy toàn bộ quá trình
async function scrapeAllPagesAndArticles() {
console.log('%cBước 1: Bắt đầu thu thập link bài viết từ tất cả các trang...', 'color: blue; font-size: 16px;');
let allArticleUrls = [];
let currentPageUrl = window.location.href;
let hasNextPage = true;
let pageCount = 1;
// Vòng lặp để đi qua tất cả các trang
while (hasNextPage) {
console.log(`-- Đang quét trang số ${pageCount}: ${currentPageUrl}`);
try {
const response = await fetch(currentPageUrl);
const text = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
const linksOnPage = Array.from(doc.querySelectorAll(".left-news .title")).map(el => el.href);
allArticleUrls.push(...linksOnPage);
// Tìm chính xác nút "Sau" dựa trên phân tích ở Bước 1
const nextButton = Array.from(doc.querySelectorAll('form[name="theForm"] a')).find(a => a.innerText.trim() === 'Sau');
if (nextButton && nextButton.href) {
currentPageUrl = nextButton.href;
pageCount++;
} else {
console.log("Không tìm thấy nút 'Sau'. Đã đến trang cuối cùng. Dừng thu thập.");
hasNextPage = false;
}
await new Promise(resolve => setTimeout(resolve, 500));
} catch (error) {
console.error(`Lỗi khi quét trang: ${currentPageUrl}`, error);
hasNextPage = false;
}
}
allArticleUrls = [...new Set(allArticleUrls)]; // Lọc bỏ các link trùng lặp
console.log(`%cHoàn tất Bước 1! Tìm thấy tổng cộng ${allArticleUrls.length} bài viết.`, 'color: green; font-size: 16px;');
// Bước 2: Bắt đầu lấy dữ liệu chi tiết
if (allArticleUrls.length === 0) return;
console.log('%cBước 2: Bắt đầu lấy dữ liệu chi tiết của từng bài viết...', 'color: blue; font-size: 16px;');
const allArticlesData = [];
for (let i = 0; i < allArticleUrls.length; i++) {
const url = allArticleUrls[i];
console.log(`Đang xử lý bài ${i + 1}/${allArticleUrls.length}`);
const data = await getArticleData(url);
if (data) {
allArticlesData.push(data);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log(`%cHOÀN TẤT! Đã lấy thành công dữ liệu của ${allArticlesData.length} bài viết.`, 'color: green; font-size: 16px;');
if (allArticlesData.length > 0) {
console.log('Dữ liệu JSON đã có trong Console. Bạn có thể copy thủ công.');
console.log(JSON.stringify(allArticlesData, null, 2));
}
}
// Bắt đầu chạy!
scrapeAllPagesAndArticles();
Sau khi chạy xong, toàn bộ dữ liệu sẽ được in ra Console dưới định dạng JSON. Bạn chỉ cần copy khối dữ liệu này, dán vào một trình soạn thảo văn bản và lưu lại dưới dạng file .json
là có thể sử dụng được.
Lời kết
Vậy là chỉ với trình duyệt và một chút kiến thức JavaScript, bạn đã có thể xây dựng một công cụ cào dữ liệu mạnh mẽ, có khả năng tự động hóa các tác vụ nhàm chán và thu thập được những thông tin quý giá.
Tuy nhiên, hãy nhớ rằng “quyền năng càng lớn, trách nhiệm càng cao”. Khi cào dữ liệu, hãy thực hiện một cách có trách nhiệm, tránh làm quá tải máy chủ của trang web bằng cách thêm các khoảng nghỉ giữa các yêu cầu.
Chúc các bạn thành công và “thu hoạch” được nhiều dữ liệu bổ ích!