Hôm trước khi cần crawl B*teB*teGo, một nền tảng học system design khá hay, mình đã đụng ngay vào bức tường mà sớm hay muộn mọi người viết scraper cũng gặp: Single Page Application.
Dưới đây là những gì mình đúc kết được.
Vấn đề với SPA
B*teB*teGo được xây dựng trên React. Khi mở View Page Source, bạn sẽ thấy thứ này:
<!DOCTYPE html>
<html>
<head>...</head>
<body>
<div id="root"></div>
<script src="/static/js/main.chunk.js"></script>
</body>
</html>
Không có nội dung gì cả. Chỉ là một cái vỏ. Toàn bộ tiêu đề, bài viết, hình ảnh đều được render phía client sau khi JavaScript chạy xong.
Điều này khiến stack scraping truyền thống hoàn toàn vô dụng:
# Cách này không lấy được gì trên một React site
import requests
from bs4 import BeautifulSoup
res = requests.get("https://b\*teb\*teg\*.com/courses/system-design-interview/scale-from-zero-to-millions-of-users")
soup = BeautifulSoup(res.text, "html.parser")
print(soup.find("article")) # None
Hai hướng tiếp cận
Phương án 1: Selenium hoặc Playwright
Giải pháp phổ biến cho SPA scraping là dùng trình duyệt thật. Playwright là lựa chọn hiện đại nhất hiện nay.
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://b\*teb\*teg\*.com/...")
page.wait_for_selector("article") # đợi JS render xong
content = page.inner_html("article")
browser.close()
Cách này hoạt động, nhưng đi kèm nhiều chi phí thực tế:
- Khám phá URL: Bạn phải xây dựng crawler để duyệt toàn bộ cây URL. B*teB*teGo có nội dung lồng nhau qua nhiều topic, chapter, lesson. Map hết chúng một cách đáng tin cậy tốn không ít thời gian.
- Edge cases: Lazy-loaded images, nội dung yêu cầu đăng nhập, auth cookie, navigation state. Mỗi thứ là một cái hố không đáy.
- Bảo trì: SPA thường xuyên thay đổi cấu trúc DOM. Selector của bạn sẽ âm thầm gãy bất cứ lúc nào.
Đáng đầu tư nếu bạn cần một production scraper chạy liên tục. Nhưng overkill cho một job thu thập dữ liệu một lần.
Phương án 2: Bán tự động với Console Script + SingleFile (khuyến nghị)
B*teB*teGo tổ chức nội dung theo Topic, mỗi Topic tương ứng một URL riêng biệt. Cấu trúc này cho phép một hướng tiếp cận đơn giản hơn nhiều.
Bước 1: Trích xuất toàn bộ URL từ browser console
Đăng nhập vào B*teB*teGo, mở trang khóa học, rồi mở DevTools > Console và chạy:
// Lấy tất cả link bài học trong sidebar navigation
const links = [...document.querySelectorAll('nav a[href]')]
.map(a => a.href)
.filter(href => href.includes('/courses/'))
.filter((v, i, arr) => arr.indexOf(v) === i); // loại bỏ trùng lặp
console.log(links.join('\n'));
// Copy output ra
Trong vài giây, bạn có ngay danh sách đầy đủ URL của mọi bài học, không cần viết crawler.
Bước 2: Mở hàng loạt tab
Dán các URL vào đoạn script mở tab theo batch:
// Chạy trong console, mở 20 tab cùng lúc
const urls = `
https://b\*teb\*teg\*.com/courses/...
https://b\*teb\*teg\*.com/courses/...
`.trim().split('\n');
const BATCH_SIZE = 20; // giữ thấp để tránh tràn RAM
urls.slice(0, BATCH_SIZE).forEach(url => window.open(url, '_blank'));
Giữ mỗi batch ở mức 10-30 tab. Mở 100+ tab cùng lúc sẽ khiến trình duyệt crash hoặc kích hoạt rate limiting.
Bước 3: Lưu bằng SingleFile
Cài extension SingleFile cho trình duyệt. Sau khi batch tab đã load xong:
- Click icon SingleFile > Save tabs > Save all tabs
- Mỗi tab được lưu thành một file
.htmlđộc lập, với CSS, ảnh và nội dung đều được nhúng inline
Output rất gọn, portable và đọc được offline. Không phụ thuộc, không broken image link.
So sánh
| Playwright | Bán tự động | |
|---|---|---|
| Thời gian setup | Vài giờ | ~15 phút |
| Xử lý auth | Có (nhưng phải làm thêm) | Có (đã đăng nhập sẵn) |
| Tự động hoàn toàn | Có | Không |
| Độ dễ gãy | Trung bình (thay đổi selector) | Thấp |
| Phù hợp cho | Pipeline tự động hóa | Thu thập dữ liệu một lần |
Nên chọn cái nào?
Dùng Playwright khi bạn cần scraping tự động, lặp lại ở quy mô lớn: CI pipeline, scheduled job, site có hàng nghìn trang.
Dùng phương án bán tự động khi bạn chỉ cần dữ liệu một lần (hoặc thỉnh thoảng) từ một site bạn đã đăng nhập và nội dung có cấu trúc URL có thể khám phá được. Bạn đánh đổi tự động hóa để lấy tốc độ tiếp cận dữ liệu.
Với B*teB*teGo cụ thể, phương án bán tự động mất khoảng 20 phút từ đầu đến cuối. Viết một Playwright crawler đủ robust cho cùng site đó sẽ mất phần lớn một ngày làm việc.
Extension SingleFile: github.com/gildas-lormeau/SingleFile