Headless? Đúng vậy, điều đó có nghĩa là trình duyệt này không có giao diện người dùng đồ họa (GUI). Thay vì sử dụng chuột hoặc thiết bị cảm ứng để tương tác với các yếu tố trực quan, bạn sử dụng giao diện dòng lệnh (CLI) để thực hiện tự động hóa.
Nhiều công cụ web scraping có sẵn cho headlesschrome, và headlesschrome thường giúp giảm bớt rất nhiều phiền toái.
Bạn cũng có thể thích: Làm thế nào để phát hiện và chống phát hiện headlesschrome?
Puppeteer là gì? Đó là một thư viện Node.js cung cấp API cấp cao để điều khiển headlesschrome hoặc Chromium hoặc tương tác với DevTools Protocol.
Hôm nay, chúng ta sẽ khám phá headlesschrome sâu hơn với Puppeteer.
Như bạn có thể tưởng tượng, có nhiều lợi ích lớn khi sử dụng Puppeteer cho web scraping:
Trong ví dụ dưới đây, chúng ta sẽ thực hiện web scraping cơ bản để giúp bạn bắt đầu nhanh chóng với Puppeteer. Trang mà chúng tôi chọn để thu thập dữ liệu là phần đánh giá của sản phẩm Apple AirPods Pro trên Amazon.
Nhưng đừng lo, trước đó, chúng ta vẫn cần một số chuẩn bị:
Nếu chưa, hãy cài đặt Node.js (LST) trực tiếp, sau đó cài đặt Puppeteer thông qua trình quản lý gói npm của Node.js. Quy trình này có thể mất một chút thời gian vì Puppeteer cũng cần cài đặt Chrome tương ứng.
npm i puppeteer
Bạn cũng có thể sử dụng demo này để hiểu tổng quan về Puppeteer. Đừng mắc kẹt ở đây, vì chúng tôi sẽ giới thiệu chi tiết về cách sử dụng Puppeteer và các kịch bản liên quan sau đó.
Puppeteer mặc định được bật chế độ headless. Ở đây, headless được tắt qua puppeteer.launch({ headless: false }) để bạn có thể thấy quá trình thu thập dữ liệu.
import puppeteer from 'puppeteer';
// Khởi chạy trình duyệt và mở một tab mới
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
// Điều hướng đến một URL được chỉ định
await page.goto('https://developer.chrome.com/');
// Đặt kích thước màn hình
await page.setViewport({width: 1080, height: 1024});
// Xác định phần tử ô tìm kiếm và nhập nội dung vào ô tìm kiếm
await page.locator('.devsite-search-field').fill('automate beyond recorder');
// Chờ và nhấp vào kết quả tìm kiếm đầu tiên
await page.locator('.devsite-result-item-link').click();
// Xác định tiêu đề đầy đủ bằng cách sử dụng chuỗi duy nhất
const textSelector = await page
.locator('text/Customize and automate')
.waitHandle();
const fullTitle = await textSelector?.evaluate(el => el.textContent);
// In tiêu đề đầy đủ
console.log('The title of this blog post is "%s".', fullTitle);
// Đóng phiên bản trình duyệt
await browser.close();
Puppeteer là thư viện bất đồng bộ promise chạy thông qua async await, có thể trình bày các chức năng của nó rất trực quan. Demo trên và các ví dụ sau không cần các hàm async. Điều này là vì "type": "module" trong package.json được thiết lập để chạy dưới dạng ES Modules.
Được rồi, chúng ta hãy bắt đầu.
Vui lòng mở khu vực bình luận của Apple AirPods Pro trước, và sau đó chúng ta cần xác định các phần tử mà chúng ta muốn thu thập nội dung. Bạn có thể mở Devtools bằng cách nhấn Ctrl + Shift + I (Windows/Linux) hoặc Cmd + Option + I (Mac).

Puppeteer hỗ trợ nhiều phương pháp chọn phần tử (puppeteer selectors), nhưng phương pháp đơn giản CSS là phương pháp được khuyến nghị nhất để bắt đầu. .devsite-search-field được sử dụng ở trên cũng là một bộ chọn CSS.

Đối với các cấu trúc CSS phức tạp, bảng điều khiển gỡ lỗi có thể sao chép trực tiếp các bộ chọn CSS. Nhấp chuột phải vào mã HTML của phần tử cần lấy để mở menu > Sao chép > Sao chép bộ chọn.
Nhưng không khuyến khích bạn làm điều này, vì các bộ chọn sao chép từ các cấu trúc phức tạp rất khó đọc và không thuận lợi cho việc bảo trì mã. Tất nhiên, điều đó hoàn toàn ổn cho một số lựa chọn đơn giản và thử nghiệm cá nhân.
Bây giờ, bộ chọn phần tử đã được xác định. Chúng ta có thể sử dụng Puppeteer để thử lấy tên người dùng tôi đã chọn ở trên.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(
`https://www.amazon.com/Apple-Generation-Cancelling-Transparency-Personalized/product-reviews/B0CHWRXH8B/ref=cm_cr_dp_d_show_all_btm?ie=UTF8&reviewerType=all_reviews`
);
const username = await page.$eval('div[data-hook="genome-widget"] .a-profile-name', node => node.textContent)
console.log('[username]===>', username);
Như bạn có thể thấy, mã trên sử dụng page.goto để chuyển đến trang được chỉ định. Sau đó, page.$eval có thể lấy nút phần tử đầu tiên được khớp và lấy các thuộc tính cụ thể của nút phần tử thông qua hàm callback.
Nếu bạn may mắn không kích hoạt trang xác minh của Amazon, bạn có thể lấy giá trị thành công. Tuy nhiên, một tập lệnh ổn định không thể chỉ dựa vào may mắn, vì vậy chúng ta vẫn cần thực hiện một số tối ưu hóa tiếp theo.
Mặc dù chúng ta đã lấy được thông tin của nút phần tử thông qua phương pháp trên, chúng ta phải xem xét các yếu tố khác: chẳng hạn như tốc độ tải mạng, trang có cuộn đến phần tử mục tiêu để tải phần tử đúng cách hay không, và có phải trang xác minh bị kích hoạt và cần được xử lý thủ công hay không.
Vì vậy, trước khi quá trình tải hoàn tất, chúng ta phải kiên nhẫn chờ đợi. Tất nhiên, Puppeteer cũng cung cấp cho chúng ta các API tương ứng để sử dụng.
API thường được sử dụng waitForSelector là một API chờ phần tử xuất hiện. Chúng ta có thể sử dụng nó để tối ưu hóa mã trên để đảm bảo tính ổn định của tập lệnh. Chỉ cần sử dụng API waitForSelector trước khi gọi page.$eval.
Bằng cách này, Puppeteer sẽ chờ trang tải phần tử div[data-hook="genome-widget"] .a-profile-name trước khi thực hiện mã tiếp theo.
await page.waitForSelector('div[data-hook="genome-widget"] .a-profile-name');
const username = await page.$eval('div[data-hook="genome-widget"] .a-profile-name', node => node.textContent)
Cũng có một số API chờ khác cho các kịch bản khác nhau. Hãy xem một số cái phổ biến:
page.waitForFunction(pageFunction, options, ...args): Chờ cho hàm được chỉ định trả về true trong ngữ cảnh trang.import puppeteer from 'puppeteer'
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
// Chờ tiêu đề của trang thay đổi thành "Example Domain"
await page.waitForFunction('document.title === "Example Domain"');
console.log('Title has changed to "Example Domain"');
await browser.close();
page.waitForNavigation(options): Chờ hoàn tất điều hướng trang. Điều hướng có thể là nhấp vào liên kết, gửi mẫu, gọi window.location, v.v.import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
// Nhấp vào liên kết và chờ điều hướng hoàn tất
await Promise.all([
page.click('a'),
page.waitForNavigation()
])
console.log('Navigation complete');
await browser.close();
page.waitForRequest(urlOrPredicate, options): Chờ các yêu cầu khớp với URL hoặc hàm điều kiện được chỉ định.import puppeteer from "puppeteer";
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://example.com');
// Yêu cầu bạn theo dõi URL yêu cầu thực tế của trang. Đây chỉ là ví dụ.
// Nhưng bạn có thể nhập https://example.com/resolve vào thanh địa chỉ của trình duyệt để kích hoạt yêu cầu và xác minh demo này
const Request = await page.waitForRequest('https://example.com/resolve');
console.log('request-url:', Request.url());
await browser.close()
page.waitForResponse(urlOrPredicate, options): Chờ phản hồi khớp với URL hoặc hàm điều kiện được chỉ định.import puppeteer from "puppeteer";
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://example.com');
// Phản hồi yêu cầu bạn theo dõi URL phản hồi thực tế của trang. Đây chỉ là ví dụ.
// Nhưng bạn có thể nhập https://example.com/resolve vào thanh địa chỉ của trình duyệt để kích hoạt phản hồi và xác minh demo này
const response = await page.waitForResponse('https://example.com/resolve');
console.log('response-status:', response.status());
await browser.close();
page.waitForNetworkIdle(options): Chờ hoạt động mạng trên trang trở nên nhàn rỗi. Phương pháp này hữu ích để đảm bảo rằng trang đã hoàn tất tải.import puppeteer from "puppeteer";
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.waitForNetworkIdle({
timeout: 30000, // Thời gian chờ tối đa 30 giây
idleTime: 500 // Tức là không có hoạt động mạng trong vòng 500 mili giây thời gian nhàn rỗi
});
console.log('Network is idle.');
// Lưu ảnh chụp màn hình để xác minh xem trang đã được tải hoàn toàn chưa
await page.screenshot({ path: 'example.png' });
await browser.close();
setTimeout: Sử dụng API Javascript trực tiếp cũng là một lựa chọn tốt. Sau khi đóng gói một chút, nó có thể được chạy trong ngữ cảnh của trang.// Chờ hai giây trước khi thực hiện script tiếp theo
await new Promise(resolve => setTimeout(resolve, 2000))
await page.click('.devsite-result-item-link'); // Nhấp vào phần tử này
Bạn có bất kỳ ý tưởng tuyệt vời hoặc nghi ngờ nào về web scraping và browserless không?
Hãy xem những gì các nhà phát triển khác đang chia sẻ trên Discord và Telegram!
Được rồi, hãy bắt đầu thu thập dữ liệu toàn bộ danh sách bình luận trên trang.
Chúng ta có thể viết lại đoạn mã trên để không chỉ thu thập một tên người dùng mà tập trung vào toàn bộ danh sách bình luận.
Đoạn mã sau cũng sử dụng page.waitForSelector để chờ đợi phần tử bình luận được tải và sử dụng page.$$ để lấy tất cả các nút phần tử phù hợp với bộ chọn phần tử:
await page.waitForSelector('div[data-hook="review"]');
const reviewList = await page.$$('div[data-hook="review"]');
Tiếp theo, chúng ta cần lặp qua danh sách phần tử bình luận và lấy thông tin cần thiết từ mỗi phần tử bình luận.
Trong đoạn mã sau, chúng ta có thể lấy textContent của tiêu đề, đánh giá, tên người dùng và nội dung, và lấy giá trị thuộc tính data-src trong nút phần tử avatar, đây là địa chỉ URL của avatar.
for (const review of reviewList) {
const title = await review.$eval(
'a[data-hook="review-title"] .cr-original-review-content',
node => node.textContent,
);
const rate = await review.$eval(
'i[data-hook="review-star-rating"] .a-icon-alt',
node => node.textContent,
);
const username = await review.$eval(
'div[data-hook="genome-widget"] .a-profile-name',
node => node.textContent,
);
const avatar = await review.$eval(
'div[data-hook="genome-widget"] .a-profile-avatar img',
node => node.getAttribute('data-src'),
);
const content = await review.$eval(
'span[data-hook="review-body"] span',
node => node.textContent,
);
console.log('[log]===>', { title, rate, username, avatar, content });
}
Khi chạy đoạn mã trên, bạn sẽ thấy thông tin ghi nhật ký được in ra trong terminal.

Nếu bạn muốn lưu trữ dữ liệu này thêm nữa, bạn có thể sử dụng module fs cơ bản của nodejs để ghi dữ liệu vào một file json để phân tích dữ liệu sau này.
Dưới đây là một hàm công cụ đơn giản:
import fs from 'fs';
function saveObjectToJson(obj, filename) {
const jsonString = JSON.stringify(obj, null, 2)
fs.writeFile(filename, jsonString, 'utf8', (err) => {
err ? console.error(err) : console.log(`File saved successfully: ${filename}`);
});
}
Đoạn mã hoàn chỉnh như sau. Sau khi chạy, bạn có thể tìm thấy amazon_reviews_log.json trong đường dẫn thực thi script hiện tại. Tệp này ghi lại tất cả các kết quả thu thập của bạn!
import puppeteer from 'puppeteer';
import fs from 'fs';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(
`https://www.amazon.com/Apple-Generation-Cancelling-Transparency-Personalized/product-reviews/B0CHWRXH8B/ref=cm_cr_dp_d_show_all_btm?ie=UTF8&reviewerType=all_reviews`
);
await page.waitForSelector('div[data-hook="review"]');
const reviewList = await page.$$('div[data-hook="review"]');
const reviewLog = []
for (const review of reviewList) {
const title = await review.$eval(
'a[data-hook="review-title"] .cr-original-review-content',
node => node.textContent,
);
const rate = await review.$eval(
'i[data-hook="review-star-rating"] .a-icon-alt',
node => node.textContent,
);
const username = await review.$eval(
'div[data-hook="genome-widget"] .a-profile-name',
node => node.textContent,
);
const avatar = await review.$eval(
'div[data-hook="genome-widget"] .a-profile-avatar img',
node => node.getAttribute('data-src'),
);
const content = await review.$eval(
'span[data-hook="review-body"] span',
node => node.textContent,
);
console.log('[log]===>', { title, rate, username, avatar, content });
reviewLog.push({ title, rate, username, avatar, content })
}
function saveObjectToJson(obj, filename) {
const jsonString = JSON.stringify(obj, null, 2)
fs.writeFile(filename, jsonString, 'utf8', (err) => {
err ? console.error(err) : console.log(`File saved successfully: ${filename}`);
});
}
saveObjectToJson(reviewLog, 'amazon_reviews_log.json')
await browser.close()
Hiểu rõ cách sử dụng cơ bản ở trên rồi chứ? Bây giờ, chúng ta có thể tiếp tục khám phá những chức năng mạnh mẽ của Puppeteer. Sau khi chạy các ví dụ sau đây, tôi tin rằng bạn sẽ có cái nhìn mới về công cụ này.
Sử dụng page.mouse.move để điều khiển chuyển động chuột của bạn.
Để bạn cảm nhận rằng con trỏ thực sự di chuyển trên trang, đoạn demo sau là một vòng lặp vô hạn, sẽ khiến chuột di chuyển ngẫu nhiên để kích hoạt kiểu hover của trang.
Lưu ý rằng điều kiện để kích hoạt hover là chuột không di chuyển quá nhanh. Cấu hình tỷ lệ di chuyển steps: 10 trong phương thức move. Và bước này cũng có thể giảm khả năng trang web bị phát hiện.
Page.evaluate là một API rất hữu ích cho phép bạn thực thi mã JavaScript chỉ chạy trong môi trường trình duyệt trong bối cảnh của trang, chẳng hạn như sử dụng window API. Mục đích ở đây là cuộn trang xuống dưới cùng để các bình luận trên trang sẽ được tải đầy đủ.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://www.google.com');
// Lấy chiều rộng và chiều cao của màn hình
const { width, height } = await page.evaluate(() => {
return { width: window.innerWidth, height: window.innerHeight };
});
// Vòng lặp vô hạn, mô phỏng chuyển động chuột ngẫu nhiên
while (true) {
const x = Math.floor(Math.random() * width);
const y = Math.floor(Math.random() * height);
await page.mouse.move(x, y, { steps: 10 });
console.log(`Vị trí chuột: (${x}, ${y})`);
await new Promise(resolve => setTimeout(resolve, 200)); // Di chuyển mỗi 0.2 giây
}
Chúng ta cũng đã gặp điều này trong demo ban đầu. Thế nào nếu thay đổi cách viết và sử dụng một số API khác để thực hiện nó?
Bạn có thể thấy rằng một số bộ chọn bên dưới được đặt trước bởi >>>, đây là bộ chọn Shadow DOM do Puppeteer cung cấp. Hầu hết các thao tác được thiết lập với một độ trễ thông qua delay, điều này có thể mô phỏng hành vi của người dùng thực tế tốt hơn. Điều này làm cho script của bạn ổn định hơn và tránh kích hoạt cơ chế chống bot của một số trang web.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
headless: false,
// defaultView đặt chiều rộng và chiều cao thành 0, có nghĩa là nội dung trang web lấp đầy toàn bộ cửa sổ.
defaultViewport: { width: 0, height: 0 }
});
const page = await browser.newPage();
await page.goto('https://developer.chrome.com/docs/css-ui?hl=de');
await page.click('>>> button[aria-controls="language-menu"]', { delay: 500 });
// Nhảy đến trang mới và chờ cho đến khi nhảy thành công
await Promise.all([
page.click('>>> li[role="presentation"]', { delay: 500 }),
page.waitForNavigation(),
])
// Sử dụng setTimeout làm trình trì hoãn để chờ 2 giây để tải trang
await new Promise(resolve => setTimeout(resolve, 2000))
// Tập trung vào hộp tìm kiếm
await page.focus('input.devsite-search-query', { delay: 500 });
// Nhập văn bản qua bàn phím
await page.keyboard.type('puppeteer', { delay: 200 });
// Kích hoạt phím enter và gửi biểu mẫu
await page.keyboard.press('Enter')
console.log('Biểu mẫu đã gửi thành công');
await page.close()
Puppeteer cung cấp một API chụp ảnh màn hình sẵn có, đây là một tính năng rất thực tế mà chúng ta đã thấy trong các ví dụ trên.
Chất lượng của tệp ảnh màn hình có thể được kiểm soát tốt thông qua chất lượng, và clip được sử dụng để cắt ảnh. Nếu bạn có yêu cầu về tỷ lệ ảnh chụp, bạn cũng có thể thiết lập defaultViewport để đạt được điều đó.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ defaultViewport: { width: 1920, height: 1080 } });
const page = await browser.newPage();
await page.goto('https://www.youtube.com/');
await page.screenshot({ path: 'screenshot1.png', });
await page.screenshot({ path: 'screenshot2.jpeg', quality: 50 });
await page.screenshot({ path: 'screenshot3.jpeg', clip: { x: 0, y: 0, width: 150, height: 150 } });
console.log('Ảnh chụp màn hình đã được lưu');
await browser.close();
Để chặn các yêu cầu, trước tiên bạn cần sử dụng setRequestInterception để kích hoạt việc chặn yêu cầu. Chạy đoạn demo sau và bạn sẽ ngạc nhiên khi thấy kiểu trang bị mất đi, và hình ảnh cũng như biểu tượng biến mất.
Điều này là vì yêu cầu được theo dõi qua trang, và resourceType và url của interceptedRequest được sử dụng để xác định xem có nên hủy bỏ hoặc viết lại yêu cầu tương ứng hay không.
Chúng ta phải lưu ý rằng phương thức isInterceptResolutionHandled nên được gọi trước khi xử lý việc chặn yêu cầu để tránh việc xử lý yêu cầu lặp lại hoặc xung đột.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
// Kích hoạt việc chặn yêu cầu
await page.setRequestInterception(true);
page.on('request', interceptedRequest => {
// Tránh yêu cầu bị xử lý lặp lại
if (interceptedRequest.isInterceptResolutionHandled()) return;
// Chặn yêu cầu và viết lại phản hồi
if (interceptedRequest.url().includes('https://fonts.gstatic.com/')) {
interceptedRequest.respond({
status: 404,
contentType: 'image/x-icon',
})
console.log('Yêu cầu biểu tượng bị chặn');
// Chặn yêu cầu kiểu
} else if (interceptedRequest.resourceType() === 'stylesheet') {
interceptedRequest.abort();
console.log('Yêu cầu stylesheet bị chặn');
// Chặn yêu cầu hình ảnh
} else if (interceptedRequest.resourceType() === 'image') {
interceptedRequest.abort();
console.log('Yêu cầu hình ảnh bị chặn');
} else {
interceptedRequest.continue();
}
});
await page.goto('https://www.youtube.com/');
Tất nhiên, các chức năng trên cũng có thể đạt được với sự trợ giúp của một số công cụ, chẳng hạn như sử dụng Nstbrowser RPA để làm cho crawler của bạn nhanh hơn!
Bước 1. Truy cập trang chính của Nstbrowser, và nhấp vào RPA/Workflow > tạo workflow.

Bước 2. Sau khi vào trang chỉnh sửa workflow, bạn có thể tái tạo các chức năng trên bằng cách kéo thả chuột.
Node bên trái có thể đáp ứng gần như tất cả nhu cầu crawler hoặc tự động hóa của bạn, và các node này rất tương thích với API của Puppeteer.
Bạn có thể hiệu chỉnh thứ tự thực thi của các node này bằng cách kết nối chúng như mã JavaScript async. Nếu bạn biết Puppeteer, bạn có thể bắt đầu nhanh chóng với chức năng Nstbrowser RPA. Đó chính là những gì bạn thấy là những gì bạn nhận được.

Bước 3. Mỗi Node có thể được cấu hình riêng, và thông tin cấu hình gần như tương ứng với cấu hình của Puppeteer.
a. Chuyển động chuột

b. Nhấp vào nút

c. Nhậpliệu

d. Phím bàn phím

e. Chờ phản hồi

f. Ảnh chụp màn hình

Ngoài ra, Nstbrowser RPA có nhiều node phổ biến và độc đáo hơn. Bạn có thể thực hiện các thao tác crawling thông thường bằng cách kéo và thả đơn giản.
HTTP headers là thông tin bổ sung được trao đổi giữa client (trình duyệt) và server. Chúng chứa metadata cho các yêu cầu và phản hồi, chẳng hạn như loại nội dung, user agent, cài đặt ngôn ngữ, v.v.
Các headers HTTP phổ biến bao gồm:
User-Agent: Xác định loại ứng dụng client, hệ điều hành, phiên bản phần mềm và các thông tin khác.Accept-Language: Chỉ ra ngôn ngữ mà client có thể hiểu và ưu tiên của nó.Referer: Chỉ ra trang nguồn của yêu cầu.Bằng cách sửa đổi các headers này, bạn có thể ngụy trang như một trình duyệt hoặc hệ điều hành khác, do đó giảm nguy cơ bị phát hiện là robot.
Khi sử dụng Puppeteer, bạn có thể sử dụng phương thức page.setExtraHTTPHeaders để thiết lập các headers trước khi nhảy đến trang web:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
// Cài đặt HTTP headers tùy chỉnh
await page.setExtraHTTPHeaders({
'Accept-Language': 'en-US,en;q=0.9',
'Referer': 'https://www.google.com',
'MyHeader': 'hello puppeteer'
});
await page.goto('https://www.httpbin.org/headers');
Nhưng nếu bạn muốn sửa đổi User-Agent, bạn không thể sử dụng phương pháp trên. Vì User-Agent trong trình duyệt có giá trị mặc định. Nếu bạn thực sự muốn thay đổi nó, bạn có thể sử dụng page.setUserAgent.
import puppeteer from "puppeteer";
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.98 Safari/537.36');
await page.goto('https://example.com/');
const navigator = await page.evaluate(_ => window.navigator.userAgent)
const platform = await page.evaluate(_ => window.navigator.platform)
console.log('userAgent: ', navigator);
console.log('platform: ', platform);
await browser.close();

Nhưng bước này vẫn chưa đủ. Từ thông tin in ra ở trên, platform vẫn được đặt thành win32 và chưa được thay đổi thực sự.
Hầu hết các trang web phát hiện thông qua window.navigator. Vì vậy, cần phải sửa đổi sâu hơn navigator. Trước khi sử dụng page.goto, chúng ta có thể sửa đổi sâu hơn navigator trong page.evaluateOnNewDocument.
Dưới đây là mô tả ngắn gọn về sự khác biệt giữa page.evaluateOnNewDocument và page.evaluate:
evaluateOnNewDocument.evaluate.await page.evaluateOnNewDocument(() => {
Object.defineProperties(navigator, {
platform: {
get: () => 'Mac'
},
});
});

Mỗi dòng trong bài viết này đóng một vai trò quan trọng để mô tả các hướng dẫn chi tiết nhất về:
Muốn thực hiện web scraping và tự động hóa một cách dễ dàng? Nstbrowser RPA giúp bạn đơn giản hóa tất cả các nhiệm vụ.