AI Automation

Chụp ảnh và xuất PDF hàng loạt bằng OpenClaw trên VPS

Công việc QA web, giám sát giao diện, hay lưu trữ báo cáo thường xuyên cần chụp ảnh hoặc xuất PDF từ nhiều trang khác nhau. Làm thủ công từng cái một là tốn thời gian, dễ thiếu sót. Bài này hướng dẫn bạn viết script Puppeteer chạy trên OpenClaw VPS để tự động chụp ảnh và xuất PDF hàng loạt từ một danh sách URL do bạn cung cấp, mỗi item xử lý độc lập, lỗi một trang không ảnh hưởng đến phần còn lại.

Yêu cầu trước khi bắt đầu

  • Một VPS đã cài OpenClaw và chạy ổn định (xem hướng dẫn cài đặt: cài OpenClaw trên VPS Ubuntu từ đầu tới lần chạy đầu tiên).
  • Bạn cần hiểu cơ bản về JSON và cách viết job trong OpenClaw.
  • Một danh sách URL cần chụp, có thể đặt trong file JSON hoặc biến môi trường.
  • Quyền root hoặc sudo để cài đặt thêm thư viện nếu cần.

Vì sao chạy tác vụ này trên OpenClaw VPS?

Puppeteer và Chromium là những công cụ nặng: Chrome headless tốn RAM và CPU khi render. Chạy trên máy local sẽ chiếm tài nguyên, nếu cần lên lịch chạy định kỳ (ví dụ mỗi sáng chụp báo cáo dashboard) thì máy tính ở nhà không phải giải pháp phù hợp. Với một VPS OpenClaw dành riêng cho automation, bạn có thể cài headless Chrome, thiết lập job chạy theo giờ, và không lo mất điện hay mạng. OpenClaw quản lý vòng đời browser và chạy script bằng Node.js, giúp việc viết logic xử lý batch URL dễ dàng.

Bước 1 - Chuẩn bị file danh sách URL

Tạo một file JSON chứa danh sách URL. Mỗi mục có thể bao gồm thông tin bổ sung như tên file đầu ra, kích thước viewport, hoặc chế độ chụp (screenshot hay PDF). Ví dụ urls.json:

[
  {
    "url": "https://example.com/dashboard",
    "name": "dashboard",
    "viewport": {"width": 1920, "height": 1080},
    "type": "pdf"
  },
  {
    "url": "https://example.com/report",
    "name": "report-page1",
    "viewport": {"width": 1440, "height": 900},
    "type": "screenshot"
  },
  {
    "url": "https://example.com/settings",
    "name": "settings",
    "viewport": {"width": 375, "height": 667},
    "type": "screenshot",
    "fullPage": true
  }
]

Bạn có thể đặt file này trong thư mục job của OpenClaw, hoặc lưu vào bộ nhớ cục bộ của VPS (ví dụ /opt/batch-urls/urls.json). Dùng ssh hoặc SFTP để upload vào VPS.

Bước 2 - Viết script Puppeteer xử lý batch

Script này đọc file JSON, lặp qua từng mục, mở URL, chờ tải xong, chụp ảnh hoặc xuất PDF, và xử lý lỗi riêng cho từng item. Nếu một URL bị timeout hoặc lỗi, vòng lặp vẫn chạy tiếp.

// batch-capture.js
const fs = require('fs');
const path = require('path');

// Trong OpenClaw, context.browser và context.puppeteer đã có
module.exports = async ({ page, context }) => {
  const urlsPath = '/opt/batch-urls/urls.json';
  const outputDir = '/opt/batch-results/';

  // Đảm bảo thư mục đầu ra tồn tại
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }

  const urls = JSON.parse(fs.readFileSync(urlsPath, 'utf8'));
  const results = [];

  for (const item of urls) {
    try {
      console.log(`[BATCH] Đang xử lý: ${item.url}`);
      await page.setViewport(item.viewport);
      await page.goto(item.url, { waitUntil: 'networkidle2', timeout: 30000 });

      // Chờ thêm 2 giây để đảm bảo trang hoàn toàn ổn định
      await page.waitForTimeout(2000);

      const fileName = `${item.name}_${Date.now()}`;
      let outputPath;

      if (item.type === 'pdf') {
        outputPath = path.join(outputDir, `${fileName}.pdf`);
        await page.pdf({
          path: outputPath,
          format: 'A4',
          printBackground: true,
          margin: { top: '1cm', right: '1cm', bottom: '1cm', left: '1cm' }
        });
      } else {
        const options = { path: path.join(outputDir, `${fileName}.png`) };
        if (item.fullPage) {
          options.fullPage = true;
        }
        outputPath = options.path;
        await page.screenshot(options);
      }

      results.push({ url: item.url, status: 'ok', file: outputPath });
      console.log(`[BATCH] ✅ Thành công: ${item.url}`);

    } catch (err) {
      console.error(`[BATCH] ❌ Lỗi: ${item.url} - ${err.message}`);
      results.push({ url: item.url, status: 'error', error: err.message });
    }
  }

  // Ghi log kết quả tổng thể
  const logFile = path.join(outputDir, `batch_log_${Date.now()}.json`);
  fs.writeFileSync(logFile, JSON.stringify(results, null, 2));
  console.log(`[BATCH] Hoàn tất. Log tại: ${logFile}`);
  return { results };
};

Giải thích script:

  • File URL đọc từ /opt/batch-urls/urls.json, bạn có thể đổi đường dẫn phù hợp với tổ chức của mình.
  • Mỗi mục trong mảng xác định rõ loại đầu ra (screenshot hay pdf) và viewport riêng.
  • page.goto chờ đến khi mạng không còn request nào trong 500ms (networkidle2), đảm bảo trang tải xong tài nguyên.
  • waitForTimeout(2000) thêm 2 giây để các script động, animation, lazy-load ảnh kịp chạy xong trước khi chụp.
  • Mỗi mục nằm trong try/catch riêng. Nếu lỗi, script ghi log và chuyển sang item tiếp theo.
  • Kết quả tổng thể được ghi vào file batch_log_*.json để dễ kiểm tra sau này.

Bước 3 - Cấu hình job trong OpenClaw

Sau khi có file script và danh sách URL, tạo một job kiểu Run script trong OpenClaw, trỏ đến file batch-capture.js. Bạn có thể dùng giao diện web hoặc API để thiết lập:

  • Trigger: chọn chạy một lần (manual) hoặc lên lịch cron nếu cần chụp định kỳ.
  • Timeout: đặt cao hơn, nếu có 10 URL, mỗi cái 30 giây, tổng thể 5 phút, vậy set timeout 10 phút là an toàn.
  • RAM: chụp PDF nhiều trang nặng, nên dùng gói 4 GB RAM trở lên cho batch tầm 50-100 URL.

Trước khi chạy thật, hãy kiểm tra dung lượng đĩa trống trên VPS, file PNG và PDF có thể lớn, đặc biệt nếu chụp full page. Dùng lệnh df -h để xem.

Bước 4 - Kiểm tra kết quả và log lỗi

Sau job chạy xong, vào thư mục /opt/batch-results/ để xem file ảnh/PDF. Dùng lệnh ls -la để liệt kê. Mở file batch_log_*.json gần nhất để xem trạng thái từng mục:

cat /opt/batch-results/batch_log_*.json | python3 -m json.tool

Kết quả thành công sẽ có "status": "ok" kèm đường dẫn file. Nếu có lỗi, bạn thấy "status": "error" và thông báo lỗi cụ thể (ví dụ: timeout, URL không truy cập được, hoặc lỗi render).

Xử lý lỗi thường gặp

Timeout khi tải trang

Một số website chậm có thể vượt quá 30 giây. Nâng timeout lên 60 giây: sửa timeout: 60000 trong page.goto. Nếu là lỗi mạng (DNS, kết nối), kiểm tra DNS và firewall trên VPS. Đảm bảo VPS đã mở port ra ngoài qua IPv4 riêng dải Việt Nam: nếu VPS có traffic quốc tế pool dùng chung khoảng 4 đến 10 Mbps, website nước ngoài có thể tải lâu hơn, hãy tăng thêm thời gian chờ.

Lỗi "Context was destroyed" hoặc "Browser closed unexpectedly"

Nguyên nhân thường do hết RAM. Với Puppeteer, mỗi trang chiếm 50-80 MB RAM cho browser process riêng. Nếu chạy batch 50 URL, RAM 2 GB dễ bị thiếu. Giải pháp: mở rộng swap trên VPS bằng file swap 2-4 GB (xem hướng dẫn cấu hình swap và tối ưu bộ nhớ cho VPS RAM thấp), hoặc nâng lên gói VPS có RAM cao hơn. Một cách khác: giảm batch xuống 10-20 URL mỗi lần chạy, hoặc dùng page.close() sau mỗi item để giải phóng context, script trên đã tái sử dụng cùng một page.

PDF xuất ra bị thiếu nội dung, trắng trang

Lỗi thường do trang chưa kịp render xong. Tăng waitForTimeout lên 5 giây (5000). Nếu trang dùng WebGL hoặc canvas nặng, có thể cần flag Chromium --disable-gpu hoặc --no-sandbox, kiểm tra cấu hình launch của OpenClaw.

Lời khuyên thực tế khi sử dụng OpenClaw cho chụp ảnh & PDF hàng loạt

Đừng chạy batch quá lớn ngay từ đầu. Thử với 5 URL trước để đo thời gian xử lý trung bình. Nếu mỗi trang mất 15 giây (tải + render + chụp), 100 URL sẽ mất 25 phút. Với VPS chạy OpenClaw tối ưu cho automation, bạn nên chia batch thành các job nhỏ hơn, mỗi job 20-30 URL, chạy tuần tự qua trigger chain. Điều này hạn chế nguy cơ timeout job, dễ gỡ lỗi hơn, và không gây áp lực quá lớn lên RAM và CPU của VPS.

Nếu output là ảnh PNG, nên dùng fullPage: true để chụp toàn bộ nội dung trang, đặc biệt hữu ích với dashboard dài. Còn với PDF, tận dụng tùy chọn printBackground: true để giữ màu nền và biểu đồ.

Câu hỏi thường gặp

Có thể chụp ảnh và PDF cùng lúc không? Mỗi URL chỉ một lần?

Có. Bạn sửa script: cho mỗi mục URL, nếu type: "both" thì sau khi page.screenshot, gọi thêm page.pdf trên cùng một trang. Chú ý: page.pdf được khuyến nghị gọi trên cùng page, không cần mở lại.

Dung lượng đĩa tối thiểu cho batch 100 URL là bao nhiêu?

Một PNG full page 1920x1080 thường 300-800 KB. PDF tương đương với ảnh có text thường 100-300 KB. Với 100 ảnh, bạn cần khoảng 50-100 MB. Tuy nhiên nếu chụp trang dài (kilo pixel), file PNG có thể lên tới 10-20 MB mỗi cái. Nên gói VPS có ít nhất 20 GB NVMe, vd gói VNLite hoặc lớn hơn, là đủ cho đa số batch.

Làm sao để biết trang đã tải xong tất cả ảnh và API?

networkidle2 của Puppeteer chờ đến khi không còn quá 2 kết nối mạng trong 500ms. Với đa số trang SPA, đây là đủ. Nếu vẫn sót, bạn có thể dùng waitForSelector cho một phần tử cụ thể, hoặc tăng waitForTimeout lên 5-10 giây.

Có thể chụp ảnh trang yêu cầu đăng nhập không?

Có. Trước khi chạy batch, bạn có thể thêm bước login vào script: dùng page.goto tới trang login, điền form, submit, lưu cookie, sau đó mới vòng lặp các URL. Ví dụ: await page.type('input[name="username"]', 'user');. Hoặc dùng context.setCookie nếu đã có cookie hợp lệ.

Script chạy OK trên máy local nhưng trên OpenClaw báo lỗi "ENOSPC"?

Lỗi hết inode hoặc hết dung lượng đĩa. Kiểm tra df -idf -h. Nếu thiếu dung lượng, dùng journalctl --vacuum-size=200M để dọn log, xóa các file đầu ra cũ không cần. Nếu thiếu inode, có thể do quá nhiều file nhỏ, hãy nén batch cũ thành tar.gz.

Bài viết liên quan

Lưu ý: Bài viết mang tính tham khảo, tổng hợp kiến thức chung. Mỗi hệ thống, hạ tầng và nhu cầu có đặc thù riêng, nên kiểm thử trong môi trường an toàn và tham vấn kỹ sư trước khi triển khai thực tế.