Giá vé máy bay là một trong những thứ biến động nhất trên internet — chúng thay đổi theo từng giờ, thậm chí từng phút, phụ thuộc vào mùa cao điểm, ngày lễ, chính sách khuyến mãi flash sale và hành vi đặt chỗ tổng thể. Theo dõi thủ công là điều không tưởng nếu bạn muốn chốt được mức giá tốt nhất. Đây chính là lúc tự động hóa phát huy tác dụng. Trong bài viết này, chúng ta sẽ xây dựng một hệ thống scraper hoàn chỉnh bằng Puppeteer — thư viện Node.js điều khiển trình duyệt Chrome không giao diện — và triển khai lên GitHub Actions để chạy tự động theo lịch mà không tốn một đồng hosting phí nào.

Tại sao chọn Puppeteer thay vì các thư viện HTTP thông thường?

Nhiều trang web đặt vé máy bay hiện đại như Google Flights, Traveloka, hay trang chính hãng VietJet đều render nội dung bằng JavaScript phía client. Nếu bạn dùng axios hay fetch đơn thuần để gửi HTTP request, bạn sẽ chỉ nhận được bộ khung HTML rỗng — không có giá tiền nào cả. Puppeteer giải quyết điều này bằng cách mở một trình duyệt Chrome thực sự (hoặc Chromium), chờ JavaScript thực thi xong, rồi mới đọc DOM. Kết quả là bạn thấy đúng thứ mà người dùng thực thấy trên màn hình.

Một ưu điểm khác của Puppeteer là khả năng mô phỏng hành vi người dùng: cuộn trang, di chuyển chuột, nhập liệu vào form — tất cả đều có thể lập trình. Điều này giúp bypass nhiều lớp kiểm tra "không phải robot" cơ bản. Dưới đây là đoạn code khởi tạo một phiên Puppeteer và điều hướng đến Google Flights để trích xuất giá:

const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());

(async () => {
  const browser = await puppeteer.launch({
    headless: 'new',
    args: [
      '--no-sandbox',
      '--disable-setuid-sandbox',
      '--disable-blink-features=AutomationControlled',
      '--window-size=1280,800',
    ],
  });

  const page = await browser.newPage();
  await page.setUserAgent(
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' +
    'AppleWebKit/537.36 (KHTML, like Gecko) ' +
    'Chrome/124.0.0.0 Safari/537.36'
  );

  // Dieu huong den Google Flights
  const url = 'https://www.google.com/travel/flights?q=Flights+to+Ho+Chi+Minh+City+from+Hanoi';
  await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });

  // Cho phan tu gia xuat hien
  await page.waitForSelector('[data-isin]', { timeout: 30000 });

  // Trich xuat text gia
  const prices = await page.$$eval('[data-isin] span[role="text"]', els =>
    els.map(el => el.innerText.trim()).filter(Boolean)
  );

  console.log('Gia tim thay:', prices.slice(0, 5));
  await browser.close();
})();

Kỹ thuật chống phát hiện bot (Anti-Bot Evasion)

Hầu hết các nền tảng thương mại điện tử lớn đều triển khai các giải pháp chống bot như Cloudflare, PerimeterX, hoặc DataDome. Chúng phát hiện scraper thông qua nhiều dấu hiệu: giá trị navigator.webdrivertrue, thiếu font nhất định, tốc độ tương tác quá nhanh, hay fingerprint canvas bất thường. Gói puppeteer-extra-plugin-stealth vá lại hầu hết các rò rỉ này. Ngoài ra, hãy thêm delay ngẫu nhiên giữa các thao tác, luân phiên User-Agent, và cân nhắc dùng proxy rotating nếu bạn chạy với tần suất cao. Điều quan trọng nhất: đặt waitUntil: 'networkidle2' để đảm bảo trang đã load xong hoàn toàn trước khi đọc dữ liệu.

Tự động hóa với GitHub Actions và cron schedule

GitHub Actions cho phép bạn chạy workflow theo lịch định kỳ hoàn toàn miễn phí trên kho lưu trữ công khai. Cấu hình workflow YAML sau đây sẽ kích hoạt scraper mỗi 6 tiếng, lưu kết quả vào file JSON và commit lên repo:

# .github/workflows/scrape-prices.yml
name: Flight Price Scraper

on:
  schedule:
    - cron: '0 */6 * * *'
  workflow_dispatch:

jobs:
  scrape:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Install Chromium
        run: npx puppeteer browsers install chrome

      - name: Run scraper
        run: node scraper.js

      - name: Commit results
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add data/prices.json
          git diff --staged --quiet || git commit -m "chore: update flight prices"
          git push
Lưu ý thực tiễn: GitHub Actions miễn phí cung cấp 2.000 phút/tháng cho repo công khai. Với workflow chạy mỗi 6 tiếng (4 lần/ngày), mỗi lần mất khoảng 3–5 phút, bạn dùng khoảng 420–600 phút/tháng — hoàn toàn nằm trong giới hạn miễn phí.

Xử lý và lưu trữ dữ liệu giá

Sau khi có mảng giá thô, bước tiếp theo là chuẩn hóa dữ liệu: loại bỏ ký tự tiền tệ, parse thành số, gắn timestamp, và lưu vào file JSON hoặc đẩy lên database như Supabase, Firebase Realtime Database, hoặc đơn giản hơn là Google Sheets qua API. Với một hệ thống đơn giản, bạn có thể dùng cấu trúc JSON phẳng: {"route":"HAN-SGN","date":"2026-08-15","price":890000,"airline":"VietJet"}. Sau nhiều tuần thu thập, bạn sẽ có đủ dữ liệu lịch sử để vẽ biểu đồ xu hướng giá và xác định cửa sổ thời gian mua vé tốt nhất — đó là bước phân tích thực sự thú vị mà chúng ta sẽ khám phá trong các bài viết tiếp theo.