Nếu bạn đang chạy các kịch bản phân tích hoặc đa tài khoản trên Playwright và khởi động một phiên bản trình duyệt riêng cho mỗi proxy — bạn đang lãng phí bộ nhớ và CPU. Playwright có khả năng chỉ định proxy ở cấp độ BrowserContext, thay vì toàn bộ trình duyệt, và điều này thay đổi kiến trúc của các kịch bản một cách triệt để: một quy trình trình duyệt, hàng chục ngữ cảnh cách ly với các IP, cookie và dấu vân tay khác nhau. Trong bài viết này — cách thức hoạt động, mã trên Python và Node.js, và lý do tại sao điều này đặc biệt quan trọng khi làm việc với proxy cư trú và di động.
Vấn đề: proxy trên browser.launch() — đắt và bất tiện
Cách tiếp cận cổ điển của nhiều kịch bản trên Playwright như sau: cho mỗi proxy, gọi chromium.launch({ proxy: {...} }), tạo ra một quy trình trình duyệt riêng, nó hoạt động, sau đó đóng lại, khởi động cái tiếp theo với proxy mới. Nếu cần xử lý 50 địa chỉ IP song song để phân tích Wildberries hoặc Ozon, điều này có nghĩa là 50 quy trình Chromium đầy đủ cùng lúc — mỗi cái tiêu tốn từ 150 đến 300 MB RAM chỉ để khởi động, chưa tính đến các tab và việc kết xuất.
Trên thực tế, điều này đã chạm đến giới hạn chỉ với 15-20 trình duyệt song song trên một VPS thông thường với 8 GB bộ nhớ. Máy chủ bắt đầu hoán đổi, các kịch bản bị rơi do hết thời gian chờ, một phần proxy không kịp xử lý. Vấn đề không phải ở proxy và cũng không phải ở Playwright — vấn đề nằm ở kiến trúc: mỗi lần khởi động trình duyệt kéo theo toàn bộ động cơ kết xuất, quy trình GPU, mạng và hàng chục luồng hỗ trợ, mặc dù thực tế bạn chỉ cần một IP mới và một phiên sạch.
Playwright giải quyết vấn đề này một cách thanh lịch: proxy có thể được chỉ định không khi khởi động trình duyệt, mà khi tạo BrowserContext — một "hộp cát" cách ly nhẹ bên trong cùng một quy trình trình duyệt. Một quy trình Chromium có thể giữ hàng chục ngữ cảnh, mỗi cái với proxy riêng, cookie và localStorage, không giao thoa với nhau.
Trình duyệt vs BrowserContext: sự khác biệt là gì
Browser — là chính quy trình của động cơ (Chromium, Firefox hoặc WebKit), được khởi động một lần và sống lâu. BrowserContext — là môi trường cách ly bên trong quy trình này, tương tự như cửa sổ riêng tư (incognito), với cookie, bộ nhớ cache, quyền hạn riêng và, điều quan trọng đối với chúng ta, các cài đặt mạng riêng, bao gồm cả proxy.
Nếu so sánh với các trình duyệt chống phát hiện như Dolphin Anty hoặc AdsPower — ở đó mỗi "hồ sơ" cũng về cơ bản là một container cách ly riêng với cookie và dấu vân tay. Playwright cho phép xây dựng logic tương tự một cách lập trình: một trình duyệt — nhiều ngữ cảnh, mỗi cái như một "hồ sơ" riêng với IP của mình.
| Tham số | Proxy trên Trình duyệt | Proxy trên BrowserContext |
|---|---|---|
| Bộ nhớ cho một IP | 150-300 MB (quy trình mới) | 15-40 MB (ngữ cảnh mới) |
| Tốc độ thay đổi IP | 2-5 giây (khởi động lại trình duyệt) | 0.1-0.3 giây (ngữ cảnh mới) |
| Cách ly cookie | Hoàn toàn (các quy trình khác nhau) | Hoàn toàn (các ngữ cảnh không chia sẻ cookie) |
| Số phiên đồng thời tối đa trên 8 GB RAM | ~20-25 | ~150-200 |
Proxy theo ngữ cảnh: mã trên Python
Dưới đây là mẫu cơ bản: một trình duyệt được khởi động một lần, sau đó cho mỗi proxy tạo một ngữ cảnh riêng. Đây là API đồng bộ của Playwright, nhưng logic giống hệt cho phiên bản async.
from playwright.sync_api import sync_playwright
proxies = [
{"server": "http://proxy1.example.com:8000", "username": "user1", "password": "pass1"},
{"server": "http://proxy2.example.com:8000", "username": "user2", "password": "pass2"},
{"server": "http://proxy3.example.com:8000", "username": "user3", "password": "pass3"},
]
with sync_playwright() as p:
# Trình duyệt được khởi động MỘT lần, không có proxy
browser = p.chromium.launch(headless=True)
for proxy in proxies:
# Proxy được chỉ định cho ngữ cảnh, không phải cho trình duyệt
context = browser.new_context(
proxy={
"server": proxy["server"],
"username": proxy["username"],
"password": proxy["password"],
},
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
)
page = context.new_page()
page.goto("https://httpbin.org/ip", timeout=30000)
print(page.content())
page.close()
context.close() # chỉ đóng ngữ cảnh, trình duyệt tiếp tục sống
browser.close()
Điểm mấu chốt — browser.launch() được gọi một lần bên ngoài vòng lặp, trong khi browser.new_context() — bên trong vòng lặp theo proxy. Đối với proxy có xác thực bằng tên đăng nhập và mật khẩu, Playwright tự động thêm tiêu đề Basic Auth ở cấp độ mạng, không cần cấu hình gì thêm.
Proxy theo ngữ cảnh: mã trên Node.js
Logic tương tự trên JavaScript với việc sử dụng async/await — thường được áp dụng kết hợp với các framework phân tích trên Node.js:
const { chromium } = require('playwright');
const proxies = [
{ server: 'http://proxy1.example.com:8000', username: 'user1', password: 'pass1' },
{ server: 'http://proxy2.example.com:8000', username: 'user2', password: 'pass2' },
];
(async () => {
const browser = await chromium.launch({ headless: true });
for (const proxy of proxies) {
const context = await browser.newContext({
proxy: {
server: proxy.server,
username: proxy.username,
password: proxy.password,
},
});
const page = await context.newPage();
await page.goto('https://httpbin.org/ip', { timeout: 30000 });
console.log(await page.content());
await page.close();
await context.close();
}
await browser.close();
})();
Để xử lý song song hàng chục proxy, các ngữ cảnh có thể được tạo ra thông qua Promise.all, mà không cần chờ đợi việc đóng ngữ cảnh trước đó — đây chính là lợi thế chính của phương pháp "một trình duyệt, nhiều ngữ cảnh" so với việc khởi động các quy trình riêng biệt.
Xoay vòng proxy sạch mà không cần khởi động lại trình duyệt
"Xoay vòng sạch" có nghĩa là khi thay đổi IP, không còn dấu vết nào từ phiên trước — không cookie, không bộ nhớ cache, không service worker. Khi khởi động lại toàn bộ trình duyệt, điều này được đảm bảo tự động, nhưng tốn tài nguyên. Khi làm việc với các ngữ cảnh, điều tương tự đạt được bằng cách đóng ngữ cảnh cũ và tạo ngữ cảnh mới — thao tác này chỉ mất vài phần giây.
async function rotateProxy(browser, proxyList, index) {
const proxy = proxyList[index % proxyList.length];
const context = await browser.newContext({ proxy });
const page = await context.newPage();
return { context, page };
}
// Sử dụng như sau: ở mỗi N lần lặp, thay đổi IP
for (let i = 0; i < 100; i++) {
if (i % 10 === 0) {
const { context, page } = await rotateProxy(browser, proxies, i);
// làm việc với phiên mới 10 yêu cầu
}
}
Mẫu này đặc biệt hữu ích khi phân tích các thị trường: ví dụ, khi thu thập giá từ Wildberries hoặc Ozon, nơi bảo vệ chống bot theo dõi số lượng yêu cầu từ một IP trong một khoảng thời gian. Xoay vòng ở cấp độ ngữ cảnh cho phép thay đổi IP mỗi 5-10 yêu cầu mà không mất hiệu suất vào việc khởi động lại trình duyệt.
Đối với các kịch bản như vậy, proxy cư trú rất phù hợp — chúng cung cấp IP của người dùng thực, mà các thị trường ít khi đánh dấu là đáng ngờ, khác với các dải IP rõ ràng từ máy chủ.
So sánh về bộ nhớ và tốc độ
Trên một VPS thử nghiệm với 8 GB RAM và 4 vCPU, đã thực hiện kiểm tra hai phương pháp trong nhiệm vụ truy cập 100 trang với các IP khác nhau. Trong trường hợp đầu tiên, cho mỗi IP, một chromium.launch() được khởi động riêng, trong trường hợp thứ hai — một trình duyệt với việc tạo 100 ngữ cảnh một cách tuần tự.
| Chỉ số | 100 lần khởi động browser.launch() | 100 ngữ cảnh trong 1 trình duyệt |
|---|---|---|
| Bộ nhớ tối đa | ~6.8 GB | ~1.9 GB |
| Thời gian thực hiện tổng cộng | 182 giây | 97 giây |
| Số phiên bị rơi do hết thời gian chờ | 7 | 0 |
Tiết kiệm bộ nhớ trong bài kiểm tra này đạt khoảng 70%, và tổng thời gian thực hiện giảm gần một nửa nhờ vào việc không có chi phí khởi động và kết thúc quy trình Chromium mỗi lần. Ở quy mô lớn — hàng ngàn IP mỗi ngày — sự khác biệt trở nên rõ ràng hơn, và điều này ảnh hưởng trực tiếp đến số lượng proxy bạn có thể sử dụng trên một máy chủ mà không cần nâng cấp phần cứng.
Các trường hợp thực tế: phân tích và đa tài khoản
Phân tích các thị trường. Khi thu thập giá từ Wildberries, Ozon hoặc Avito, cùng một trình duyệt giữ mở hàng chục ngữ cảnh, mỗi cái với IP duy nhất và kho cookie riêng. Điều này giảm thiểu rủi ro bị chặn theo mẫu yêu cầu và cho phép thu thập dữ liệu nhanh hơn, không cần đợi khởi động lại quy trình giữa các IP.
Đa tài khoản và nuôi tài khoản. Để thử nghiệm các chiến dịch quảng cáo trên Facebook Ads hoặc TikTok Ads thông qua các kịch bản Playwright, mỗi tài khoản hợp lý nên được giữ trong một ngữ cảnh riêng với proxy và user-agent của riêng mình — điều này mô phỏng hành vi của các trình duyệt chống phát hiện như Multilogin hoặc GoLogin, chỉ khác là không cần giấy phép cho chính trình duyệt chống phát hiện, hoàn toàn bằng lập trình.
Kiểm tra quảng cáo theo địa lý. Các nhà tiếp thị cần kiểm tra cách hiển thị sáng tạo ở các quốc gia khác nhau, các ngữ cảnh với các proxy khác nhau cho phép "ghé thăm" các khu vực cần thiết trong một phiên trình duyệt mà không cần mở hàng chục cửa sổ riêng lẻ bằng tay.
Đối với các kịch bản mà việc mô phỏng lưu lượng di động là quan trọng — ví dụ, kiểm tra phiên bản di động của trang web hoặc quảng cáo trên TikTok Ads — nên xem xét proxy di động, cung cấp IP từ các nhà mạng thực và ít gây nghi ngờ hơn với các hệ thống chống gian lận.
Nếu nhiệm vụ là phân tích nhanh và ổn định mà không cần độ ẩn danh cao (ví dụ, thu thập dữ liệu công khai từ các trang web không có bảo vệ chống bot nghiêm ngặt), proxy từ trung tâm dữ liệu cung cấp tỷ lệ tốt nhất giữa tốc độ và chi phí cho các khối lượng yêu cầu lớn.
Những lỗi thường gặp và cách tránh
1. Proxy được chỉ định trong launch(), không phải trong new_context(). Đây là lỗi phổ biến nhất của người mới — proxy được chỉ định khi khởi động trình duyệt sẽ được áp dụng cho tất cả các ngữ cảnh ngay lập tức và không thể được ghi đè một cách riêng lẻ. Luôn luôn truyền proxy vào new_context(), nếu bạn cần tính linh hoạt về IP.
2. Không đóng các ngữ cảnh cũ. Nếu tạo hàng trăm ngữ cảnh và không gọi context.close(), bộ nhớ của trình duyệt sẽ tăng lên theo tuyến tính, cho đến khi quy trình bị rơi do OOM. Luôn đóng ngữ cảnh sau khi hoàn thành nhiệm vụ cho IP này.
3. Một user-agent cho tất cả các ngữ cảnh. Nếu proxy thay đổi nhưng dấu vân tay của trình duyệt vẫn giống nhau (user-agent, ngôn ngữ, múi giờ), các hệ thống chống bot của các thị trường và mạng xã hội vẫn có thể liên kết các phiên với nhau. Thay đổi user-agent và viewport cùng với proxy cho mỗi ngữ cảnh.
4. Bỏ qua thời gian chờ của proxy. Proxy cư trú và di động đôi khi phản hồi chậm hơn proxy từ trung tâm dữ liệu do cơ sở hạ tầng mạng thực tế. Đặt thời gian chờ hợp lý (20-40 giây) khi gọi page.goto(), nếu không kịch bản sẽ coi proxy hoạt động là "chết".
5. Quá nhiều ngữ cảnh trong một trình duyệt cùng lúc. Mặc dù tiết kiệm bộ nhớ, việc tạo không kiểm soát 500+ ngữ cảnh hoạt động cùng lúc vẫn gặp giới hạn của quy trình. Cân bằng hợp lý — 30-80 ngữ cảnh hoạt động đồng thời cho một quy trình trình duyệt, tùy thuộc vào dung lượng RAM của máy chủ.
Kết luận
Việc chỉ định proxy ở cấp độ BrowserContext, thay vì cho toàn bộ trình duyệt — là một giải pháp kiến trúc tiết kiệm đến 70% bộ nhớ, tăng tốc độ xoay vòng IP lên hàng chục lần và cung cấp cách ly cookie sạch giữa các phiên mà không cần khởi động lại Chromium liên tục. Đối với việc phân tích các thị trường, thử nghiệm quảng cáo ở các khu vực khác nhau và đa tài khoản, đây là cách trực tiếp để tăng số lượng phiên đồng thời trên cùng một máy chủ mà không cần nâng cấp phần cứng.
Nếu bạn đang xây dựng một kịch bản trên Playwright để thu thập dữ liệu từ Wildberries, Ozon hoặc để làm việc với các bảng quảng cáo Facebook Ads và TikTok Ads, chất lượng của chính pool proxy không kém phần quan trọng so với kiến trúc mã. Đối với các nhiệm vụ mà độ tin cậy của IP và khả năng chống lại các hệ thống chống bot là quan trọng, nên bắt đầu với proxy cư trú — chúng rất phù hợp với phương pháp "một trình duyệt, nhiều ngữ cảnh" đã được mô tả ở trên.