Scraping BrowserHướng dẫnTối ưu hóa chi phí

Các giải pháp tối ưu hóa lưu lượng truy cập proxy trình duyệt khi scraping

Giới thiệu

Khi sử dụng Puppeteer để scraping dữ liệu, việc tiêu thụ lưu lượng là một yếu tố cần cân nhắc quan trọng. Đặc biệt khi sử dụng dịch vụ proxy, chi phí lưu lượng có thể tăng lên đáng kể. Để tối ưu hóa việc sử dụng lưu lượng, chúng ta có thể áp dụng các chiến lược sau:

  1. Chặn tài nguyên: Giảm tiêu thụ lưu lượng bằng cách chặn các yêu cầu tài nguyên không cần thiết.
  2. Chặn URL yêu cầu: Giảm thêm lưu lượng bằng cách chặn các yêu cầu cụ thể dựa trên đặc điểm URL.
  3. Mô phỏng thiết bị di động: Sử dụng cấu hình thiết bị di động để lấy các phiên bản trang nhẹ hơn.
  4. Tối ưu hóa toàn diện: Kết hợp các phương pháp trên để đạt được kết quả tốt nhất.

Phương án tối ưu hóa 1: Chặn tài nguyên

Giới thiệu về chặn tài nguyên

Trong Puppeteer, page.setRequestInterception(true) có thể bắt giữ mọi yêu cầu mạng được khởi tạo bởi trình duyệt và quyết định tiếp tục (request.continue()), hủy bỏ (request.abort()), hoặc tùy chỉnh phản hồi (request.respond()).

Phương pháp này có thể giảm đáng kể mức tiêu thụ băng thông, đặc biệt phù hợp với các kịch bản thu thập dữ liệu, chụp ảnh màn hìnhtối ưu hóa hiệu suất.

Các loại tài nguyên có thể chặn và đề xuất

Loại tài nguyênMô tảVí dụTác động sau khi chặnĐề xuất
imageTài nguyên hình ảnhHình ảnh JPG/PNG/GIF/WebPHình ảnh sẽ không được hiển thị⭐ An toàn
fontTệp phông chữPhông chữ TTF/WOFF/WOFF2Sẽ sử dụng phông chữ mặc định của hệ thống⭐ An toàn
mediaTệp đa phương tiệnTệp video/âm thanhNội dung đa phương tiện không thể phát⭐ An toàn
manifestWeb App ManifestTệp cấu hình PWAChức năng PWA có thể bị ảnh hưởng⭐ An toàn
prefetchTài nguyên prefetch<link rel="prefetch">Tác động tối thiểu đến trang⭐ An toàn
stylesheetTệp kiểu CSSTệp CSS bên ngoàiKiểu trang bị mất, có thể ảnh hưởng đến bố cục⚠️ Cần thận trọng
websocketWebSocketKết nối giao tiếp thời gian thựcChức năng thời gian thực bị vô hiệu hóa⚠️ Cần thận trọng
eventsourceServer-Sent EventsDữ liệu push từ serverChức năng push bị vô hiệu hóa⚠️ Cần thận trọng
preflightYêu cầu preflight CORSYêu cầu OPTIONSYêu cầu cross-origin thất bại⚠️ Cần thận trọng
scriptScript JavaScriptTệp JS bên ngoàiChức năng động bị vô hiệu hóa, SPA có thể không render❌ Tránh
xhrYêu cầu XHRYêu cầu dữ liệu AJAXKhông thể lấy dữ liệu động❌ Tránh
fetchYêu cầu FetchYêu cầu AJAX hiện đạiKhông thể lấy dữ liệu động❌ Tránh
documentTài liệu chínhTrang HTML chínhTrang không thể tải❌ Tránh

Giải thích mức độ đề xuất:

  • An toàn: Chặn có hầu như không ảnh hưởng đến việc scraping dữ liệu hoặc render màn hình đầu tiên; nên chặn theo mặc định.
  • ⚠️ Cần thận trọng: Có thể làm hỏng kiểu dáng, chức năng thời gian thực hoặc yêu cầu cross-origin; cần có sự đánh giá của doanh nghiệp.
  • Tránh: Xác suất cao gây ra việc SPA/các trang động không render hoặc lấy dữ liệu bình thường, trừ khi bạn hoàn toàn chắc chắn rằng bạn không cần các tài nguyên này.

Ví dụ mã chặn tài nguyên

import puppeteer from 'puppeteer-core';
 
const scrapelessUrl = 'wss://browser.scrapeless.com/browser?token=your_api_key&session_ttl=180&proxy_country=ANY';
 
async function scrapeWithResourceBlocking(url) {
    const browser = await puppeteer.connect({
        browserWSEndpoint: scrapelessUrl,
        defaultViewport: null
    });
    const page = await browser.newPage();
 
    // Bật chặn yêu cầu
    await page.setRequestInterception(true);
 
    // Định nghĩa các loại tài nguyên cần chặn
    const BLOCKED_TYPES = new Set([
        'image',
        'font',
        'media',
        'stylesheet',
    ]);
 
    // Chặn yêu cầu
    page.on('request', (request) => {
        if (BLOCKED_TYPES.has(request.resourceType())) {
            request.abort();
            console.log(`Blocked: ${request.resourceType()} - ${request.url().substring(0, 50)}...`);
        } else {
            request.continue();
        }
    });
 
    await page.goto(url, {waitUntil: 'domcontentloaded'});
 
    // Trích xuất dữ liệu
    const data = await page.evaluate(() => {
        return {
            title: document.title,
            content: document.body.innerText.substring(0, 1000)
        };
    });
 
    await browser.close();
    return data;
}
 
// Sử dụng
scrapeWithResourceBlocking('https://www.scrapeless.com')
    .then(data => console.log('Scraping result:', data))
    .catch(error => console.error('Scraping failed:', error));

Phương án tối ưu hóa 2: Chặn URL yêu cầu

Ngoài việc chặn theo loại tài nguyên, có thể thực hiện kiểm soát chặn chi tiết hơn dựa trên đặc điểm URL. Điều này đặc biệt hiệu quả để chặn quảng cáo, script phân tích và các yêu cầu bên thứ ba không cần thiết khác.

Chiến lược chặn URL

  1. Chặn theo domain: Chặn tất cả các yêu cầu từ một domain cụ thể
  2. Chặn theo đường dẫn: Chặn các yêu cầu từ một đường dẫn cụ thể
  3. Chặn theo loại tệp: Chặn các tệp có phần mở rộng cụ thể
  4. Chặn theo từ khóa: Chặn các yêu cầu có URL chứa các từ khóa cụ thể

Các mẫu URL thường bị chặn

Mẫu URLMô tảVí dụĐề xuất
Dịch vụ quảng cáoDomain mạng quảng cáoad.doubleclick.net, googleadservices.com⭐ An toàn
Dịch vụ phân tíchScript thống kê và phân tíchgoogle-analytics.com, hotjar.com⭐ An toàn
Plugin mạng xã hộiNút chia sẻ trên mạng xã hội, v.v.platform.twitter.com, connect.facebook.net⭐ An toàn
Pixel theo dõiPixel theo dõi hành vi người dùngURL chứa pixel, beacon, tracker⭐ An toàn
Tệp đa phương tiện lớnTệp video, âm thanh lớnPhần mở rộng như .mp4, .webm, .mp3⭐ An toàn
Dịch vụ phông chữDịch vụ phông chữ trực tuyếnfonts.googleapis.com, use.typekit.net⭐ An toàn
Tài nguyên CDNCDN tài nguyên tĩnhcdn.jsdelivr.net, unpkg.com⚠️ Cần thận trọng

Ví dụ mã chặn URL

import puppeteer from 'puppeteer-core';
 
const scrapelessUrl = 'wss://browser.scrapeless.com/browser?token=your_api_key&session_ttl=180&proxy_country=ANY';
 
async function scrapeWithUrlBlocking(url) {
    const browser = await puppeteer.connect({
        browserWSEndpoint: scrapelessUrl,
        defaultViewport: null
    });
    const page = await browser.newPage();
 
    // Bật chặn yêu cầu
    await page.setRequestInterception(true);
 
    // Định nghĩa domain và mẫu URL cần chặn
    const BLOCKED_DOMAINS = [
        'google-analytics.com',
        'googletagmanager.com',
        'doubleclick.net',
        'facebook.net',
        'twitter.com',
        'linkedin.com',
        'adservice.google.com',
    ];
 
    const BLOCKED_PATHS = [
        '/ads/',
        '/analytics/',
        '/pixel/',
        '/tracking/',
        '/stats/',
    ];
 
    // Chặn yêu cầu
    page.on('request', (request) => {
        const url = request.url();
 
        // Kiểm tra domain
        if (BLOCKED_DOMAINS.some(domain => url.includes(domain))) {
            request.abort();
            console.log(`Blocked domain: ${url.substring(0, 50)}...`);
            return;
        }
 
        // Kiểm tra đường dẫn
        if (BLOCKED_PATHS.some(path => url.includes(path))) {
            request.abort();
            console.log(`Blocked path: ${url.substring(0, 50)}...`);
            return;
        }
 
        // Cho phép các yêu cầu khác
        request.continue();
    });
 
    await page.goto(url, {waitUntil: 'domcontentloaded'});
 
    // Trích xuất dữ liệu
    const data = await page.evaluate(() => {
        return {
            title: document.title,
            content: document.body.innerText.substring(0, 1000)
        };
    });
 
    await browser.close();
    return data;
}
 
// Sử dụng
scrapeWithUrlBlocking('https://www.scrapeless.com')
    .then(data => console.log('Scraping result:', data))
    .catch(error => console.error('Scraping failed:', error));

Phương án tối ưu hóa 3: Mô phỏng thiết bị di động

Mô phỏng thiết bị di động là một chiến lược tối ưu hóa lưu lượng hiệu quả khác vì các website di động thường cung cấp nội dung trang nhẹ hơn.

Ưu điểm của việc mô phỏng thiết bị di động

  1. Phiên bản trang nhẹ hơn: Nhiều website cung cấp nội dung ngắn gọn hơn cho thiết bị di động
  2. Tài nguyên hình ảnh nhỏ hơn: Phiên bản di động thường tải hình ảnh nhỏ hơn
  3. CSS và JavaScript đơn giản hơn: Phiên bản di động thường sử dụng kiểu dáng và script đơn giản hơn
  4. Giảm quảng cáo và nội dung không cốt lõi: Phiên bản di động thường loại bỏ một số chức năng không cốt lõi
  5. Phản hồi thích ứng: Lấy bố cục nội dung được tối ưu hóa cho màn hình nhỏ

Cấu hình mô phỏng thiết bị di động

Dưới đây là các tham số cấu hình cho một số thiết bị di động thường được sử dụng:

const iPhoneX = {
    viewport: {
        width: 375,
        height: 812,
        deviceScaleFactor: 3,
        isMobile: true,
        hasTouch: true,
        isLandscape: false
    }
};

Hoặc sử dụng trực tiếp các phương thức tích hợp của puppeteer để mô phỏng thiết bị di động

import { KnownDevices } from 'puppeteer-core';
const iPhone = KnownDevices['iPhone 15 Pro'];
 
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.emulate(iPhone);

Ví dụ mã mô phỏng thiết bị di động

import puppeteer, {KnownDevices} from 'puppeteer-core';
 
const scrapelessUrl = 'wss://browser.scrapeless.com/browser?token=your_api_key&session_ttl=180&proxy_country=ANY';
 
async function scrapeWithMobileEmulation(url) {
    const browser = await puppeteer.connect({
        browserWSEndpoint: scrapelessUrl,
        defaultViewport: null
    });
 
    const page = await browser.newPage();
 
    // Thiết lập mô phỏng thiết bị di động
    const iPhone = KnownDevices['iPhone 15 Pro'];
    await page.emulate(iPhone);
 
    await page.goto(url, {waitUntil: 'domcontentloaded'});
    // Trích xuất dữ liệu
    const data = await page.evaluate(() => {
        return {
            title: document.title,
            content: document.body.innerText.substring(0, 1000)
        };
    });
 
    await browser.close();
    return data;
}
 
// Sử dụng
scrapeWithMobileEmulation('https://www.scrapeless.com')
    .then(data => console.log('Scraping result:', data))
    .catch(error => console.error('Scraping failed:', error));

Ví dụ tối ưu hóa toàn diện

Dưới đây là một ví dụ toàn diện kết hợp tất cả các phương án tối ưu hóa:

import puppeteer, {KnownDevices} from 'puppeteer-core';
 
const scrapelessUrl = 'wss://browser.scrapeless.com/browser?token=your_api_key&session_ttl=180&proxy_country=ANY';
 
async function optimizedScraping(url) {
    console.log(`Starting optimized scraping: ${url}`);
 
    // Ghi lại lượng lưu lượng đã sử dụng
    let totalBytesUsed = 0;
 
    const browser = await puppeteer.connect({
        browserWSEndpoint: scrapelessUrl,
        defaultViewport: null
    });
 
    const page = await browser.newPage();
 
    // Thiết lập mô phỏng thiết bị di động
    const iPhone = KnownDevices['iPhone 15 Pro'];
    await page.emulate(iPhone);
 
    // Thiết lập chặn yêu cầu
    await page.setRequestInterception(true);
 
    // Định nghĩa các loại tài nguyên cần chặn
    const BLOCKED_TYPES = [
        'image',
        'media',
        'font'
    ];
 
    // Định nghĩa các domain cần chặn
    const BLOCKED_DOMAINS = [
        'google-analytics.com',
        'googletagmanager.com',
        'facebook.net',
        'doubleclick.net',
        'adservice.google.com'
    ];
 
    // Định nghĩa các đường dẫn URL cần chặn
    const BLOCKED_PATHS = [
        '/ads/',
        '/analytics/',
        '/tracking/'
    ];
 
    // Chặn yêu cầu
    page.on('request', (request) => {
        const url = request.url();
        const resourceType = request.resourceType();
 
        // Kiểm tra loại tài nguyên
        if (BLOCKED_TYPES.includes(resourceType)) {
            console.log(`Blocked resource type: ${resourceType} - ${url.substring(0, 50)}...`);
            request.abort();
            return;
        }
 
        // Kiểm tra domain
        if (BLOCKED_DOMAINS.some(domain => url.includes(domain))) {
            console.log(`Blocked domain: ${url.substring(0, 50)}...`);
            request.abort();
            return;
        }
 
        // Kiểm tra đường dẫn
        if (BLOCKED_PATHS.some(path => url.includes(path))) {
            console.log(`Blocked path: ${url.substring(0, 50)}...`);
            request.abort();
            return;
        }
 
        // Cho phép các yêu cầu khác
        request.continue();
    });
 
    // Giám sát lưu lượng mạng
    page.on('response', async (response) => {
        const headers = response.headers();
        const contentLength = headers['content-length'] ? parseInt(headers['content-length'], 10) : 0;
        totalBytesUsed += contentLength;
    });
 
    await page.goto(url, {waitUntil: 'domcontentloaded'});
 
    // Mô phỏng cuộn để kích hoạt nội dung tải lười
    await page.evaluate(() => {
        window.scrollBy(0, window.innerHeight);
    });
 
    await new Promise(resolve => setTimeout(resolve, 1000))
 
    // Trích xuất dữ liệu
    const data = await page.evaluate(() => {
        return {
            title: document.title,
            content: document.body.innerText.substring(0, 1000),
            links: Array.from(document.querySelectorAll('a')).slice(0, 10).map(a => ({
                text: a.innerText,
                href: a.href
            }))
        };
    });
 
    // Xuất thống kê sử dụng lưu lượng
    console.log(`\nTraffic Usage Statistics:`);
    console.log(`Used: ${(totalBytesUsed / 1024 / 1024).toFixed(2)} MB`);
 
    await browser.close();
    return data;
}
 
// Sử dụng
optimizedScraping('https://www.scrapeless.com')
    .then(data => console.log('Scraping complete:', data))
    .catch(error => console.error('Scraping failed:', error));

So sánh tối ưu hóa

Chúng ta thử loại bỏ mã tối ưu khỏi ví dụ toàn diện để so sánh lưu lượng trước và sau khi tối ưu. Dưới đây là ví dụ mã chưa tối ưu:

import puppeteer from 'puppeteer-core';
 
const scrapelessUrl = 'wss://browser.scrapeless.com/browser?token=your_api_key&session_ttl=180&proxy_country=ANY';
 
async function optimizedScraping(url) {
  console.log(`Starting optimized scraping: ${url}`);
 
  // Record traffic usage
  let totalBytesUsed = 0;
 
  const browser = await puppeteer.connect({
    browserWSEndpoint: scrapelessUrl,
    defaultViewport: null
  });
 
  const page = await browser.newPage();
 
  // Set request interception
  await page.setRequestInterception(true);
 
  // Intercept requests
  page.on('request', (request) => {
    request.continue();
  });
 
  // Monitor network traffic
  page.on('response', async (response) => {
    const headers = response.headers();
    const contentLength = headers['content-length'] ? parseInt(headers['content-length'], 10) : 0;
    totalBytesUsed += contentLength;
  });
 
  await page.goto(url, {waitUntil: 'domcontentloaded'});
 
  // Simulate scrolling to trigger lazy-loading content
  await page.evaluate(() => {
    window.scrollBy(0, window.innerHeight);
  });
 
  await new Promise(resolve => setTimeout(resolve, 1000))
 
  // Extract data
  const data = await page.evaluate(() => {
    return {
      title: document.title,
      content: document.body.innerText.substring(0, 1000),
      links: Array.from(document.querySelectorAll('a')).slice(0, 10).map(a => ({
        text: a.innerText,
        href: a.href
      }))
    };
  });
 
  // Output traffic usage statistics
  console.log(`\nTraffic Usage Statistics:`);
  console.log(`Used: ${(totalBytesUsed / 1024 / 1024).toFixed(2)} MB`);
 
  await browser.close();
  return data;
}
 
// Usage
optimizedScraping('https://www.scrapeless.com')
  .then(data => console.log('Scraping complete:', data))
  .catch(error => console.error('Scraping failed:', error));

Sau khi chạy mã chưa tối ưu, chúng ta có thể thấy sự khác biệt về lưu lượng rất trực quan từ thông tin được in ra:

Kịch bảnLưu lượng đã sử dụng (MB)Tỷ lệ tiết kiệm
Chưa tối ưu6.03
Đã tối ưu0.81≈ 86.6 %

Bằng cách kết hợp các phương án tối ưu hóa trên, việc tiêu thụ lưu lượng proxy có thể được giảm đáng kể, hiệu quả scraping được cải thiện và đảm bảo lấy được nội dung cốt lõi cần thiết.