Tìm hiểu cách trang web phát hiện Selenium, Puppeteer và Playwright qua navigator.webdriver, CDP artifacts, tính năng thiếu và dấu hiệu SwiftShader GPU.
Trình duyệt headless đang chạy ngầm sau một phần lớn hoạt động tự động hóa trên web: bộ test CI, công cụ theo dõi uptime, trình thu thập giá, và ở chiều ngược lại—các bot nhồi thông tin đăng nhập, bot mua hàng hạn chế và bot gian lận click. Công nghệ phát hiện đã tiến xa vượt ra khỏi thời kỳ mà chuỗi user-agent còn chứa nguyên văn HeadlessChrome. Ngày nay, các trang web bắt automation headless qua một mạng lưới tín hiệu nhiều lớp mà một trình duyệt được điều khiển bằng code liên tục để lộ. Hướng dẫn này giải thích chính xác những tín hiệu đó là gì và tại sao chúng rất khó triệt tiêu.
Điểm cốt lõi cần nhớ
navigator.webdriverlà cờ hiệu automation chuẩn, được đặt thànhtruebởi bất kỳ trình duyệt nào chạy dưới WebDriver; vá nó sai cách bản thân đã là một dấu vân tay.- Artifact CDP rất khó xóa hoàn toàn: Chrome DevTools Protocol để lại các biến toàn cục đặc trưng và mẫu định thời gian tồn tại dù đã qua nhiều bản vá stealth.
- Khoảng trống tính năng làm lộ môi trường headless: renderer WebGL chạy hoàn toàn bằng phần mềm, thiếu codec âm thanh, trạng thái quyền mâu thuẫn nhau, và danh sách plugin trống—tất cả đều trông rất khác so với trình duyệt desktop thực.
- Selenium, Puppeteer và Playwright để lại chữ ký riêng biệt dù user-agent đã bị giả mạo—mỗi framework có artifact đặc trưng trong phạm vi toàn cục và hành vi mạng mặc định khác nhau.
- Plugin stealth nâng cao hàng rào nhưng không thể đóng kín: chúng vá các rò rỉ ở tầng JavaScript nhưng không thể chạm tới các tín hiệu bên dưới—TLS fingerprint, thứ tự frame HTTP/2, hay cây tiến trình ở cấp hệ điều hành.
Trình duyệt headless là gì?
Trình duyệt headless chạy đầy đủ engine dựng trang—phân tích HTML, dàn trang CSS, thực thi JavaScript—mà không hiển thị gì lên màn hình. Một chương trình gửi lệnh qua giao thức điều khiển, trình duyệt phản hồi, và không cần con người quan sát. Puppeteer, Playwright và Selenium WebDriver đều dùng mô hình này, thường trên Chrome Headless (khởi động với cờ --headless) hoặc Firefox ở chế độ headless.
Chính đặc tính làm trình duyệt headless hữu ích cho kiểm thử cũng là thứ khiến nó bị phát hiện: nó được điều khiển bằng code, và code luôn để lại dấu vết.
Dấu hiệu navigator.webdriver
Tín hiệu headless trực tiếp nhất là navigator.webdriver. Đặc tả WebDriver yêu cầu mọi trình duyệt đang ở dưới quyền điều khiển WebDriver phải để lộ thuộc tính này với giá trị true. Trên trình duyệt desktop thực, nó trả về false hoặc là undefined.
// A detector reads this in a single line.
if (navigator.webdriver === true) {
// Browser is almost certainly under WebDriver control.
}
Đứng một mình, đây là tín hiệu yếu—các bản vá stealth đã triệt tiêu nó từ nhiều năm trước. Nhưng vá sai cách bản thân đã là một dấu hiệu tố cáo. Nếu property descriptor trông không chuẩn, hàm get có thể serialize được, hay Object.getOwnPropertyDescriptor(navigator, 'webdriver') trả về một object khác với những gì trình duyệt thực để lộ, một detector kỹ lưỡng sẽ nhận ra ngay. Một bản vá navigator.webdriver = false cẩu thả thường đáng ngờ hơn là để nguyên giá trị true.
Artifact CDP và DevTools Protocol
Puppeteer và Playwright đều điều khiển Chrome qua Chrome DevTools Protocol—cùng giao thức phía sau Chrome DevTools. Kênh điều khiển đó để lại artifact trong trang.
Rò rỉ biến toàn cục
Một số mẫu sử dụng CDP inject object vào phạm vi toàn cục của trang. Các phiên bản Puppeteer cũ để lộ __puppeteer_evaluation_script__; các framework khác để lại __nightmare, _phantom, hay window.callPhantom. Script phát hiện liệt kê những thứ này:
const knownArtifacts = [
'__nightmare', '_phantom', 'callPhantom',
'__puppeteer_evaluation_script__',
'__selenium_evaluate', '__webdriver_evaluate',
];
const found = knownArtifacts.filter(k => k in window);
// Any hit is a strong automation signal.
Bất thường Event Listener
Các automation framework chặn fetch hay XMLHttpRequest để chỉnh sửa response để lại mẫu trong bản đồ event listener. Detector truy vấn getEventListeners trên object window có thể thấy stack listener mà phiên duyệt web thực của người dùng không tạo ra.
Kênh side-channel định thời gian
Giao tiếp CDP có độ trễ đo được. Một chuỗi tác vụ được đo thời gian qua performance.now() có thể tiết lộ độ jitter đặc trưng xuất hiện khi mỗi bước cần một vòng khứ hồi qua kết nối CDP, thay vì chạy đồng bộ trên event loop của trình duyệt.
Chữ ký của Selenium, Puppeteer và Playwright
Cả ba framework đều cuối cùng điều khiển một trình duyệt, nhưng mỗi cái để lại dấu vết khác nhau.
| Framework | Giao diện điều khiển | Artifact đáng chú ý |
|---|---|---|
| Selenium | W3C WebDriver qua HTTP | navigator.webdriver = true (chưa vá), thứ tự HTTP header đặc trưng WebDriver từ thư viện client |
| Puppeteer | Chrome DevTools Protocol | Injection toàn cục CDP, biến window.__puppeteer_* phiên bản cũ, chỉ hỗ trợ Chrome nên TLS khớp với Chrome thực |
| Playwright | CDP (Chrome/Edge), giao thức nội bộ (Firefox/WebKit) | Mặc định sạch hơn Puppeteer; biến thể Firefox/WebKit lại khác với bản đối ứng trình duyệt thực về tập tính năng |
Playwright đã đầu tư nhiều hơn vào việc giảm rò rỉ ở cấp thấp, đó là lý do nghiên cứu phát hiện ngày nay tập trung vào các tín hiệu tinh vi hơn ở trên khi Playwright là framework bị nghi ngờ.
Tính năng trình duyệt thiếu và không nhất quán
Môi trường headless luôn khác biệt so với trình duyệt desktop thực ở nhiều mảng tính năng. Các script phát hiện thăm dò những khoảng trống này kết hợp với nhau.
Danh sách Plugin
Chrome desktop thực đi kèm một tập plugin (PDF Viewer, v.v.) có thể truy cập qua navigator.plugins. Chrome Headless lịch sử trả về mảng rỗng. Các bản build hiện đại đã cải thiện điều này, nhưng bản thân các object plugin vẫn khác nhau về property descriptor và có thể được đối chiếu với những gì trình duyệt của người dùng thực sẽ để lộ.
Codec media
Trình duyệt thực đàm phán codec với hệ điều hành và GPU bên dưới. Môi trường headless chạy không có phần cứng hiển thị thường báo cáo hỗ trợ codec hạn chế qua HTMLMediaElement.canPlayType(). Một trình duyệt tự nhận là Chrome trên Windows nhưng lại báo không thể phát video H.264 là một mâu thuẫn đáng chú ý.
WebGL và render GPU
WebGL là một trong những dấu hiệu headless mạnh nhất. Không có quyền truy cập GPU vật lý, Chrome Headless quay về SwiftShader—trình raster hóa bằng phần mềm. Một detector đọc gl.getParameter(gl.RENDERER) và kiểm tra chuỗi như SwiftShader hay Google SwiftShader thay vì tên GPU thực:
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
const renderer = gl.getParameter(gl.RENDERER);
// "ANGLE (Intel, ...)" or "Apple M2" → real hardware
// "Google SwiftShader" → headless software render
Một user-agent tự nhận là thiết bị mạnh trong khi WebGL renderer báo cáo render CPU-only bằng phần mềm là mâu thuẫn trực tiếp.
Mâu thuẫn Permissions API
Permissions API cho phép script truy vấn xem người dùng có cấp quyền truy cập vào các khả năng cụ thể hay không. Trong môi trường headless, kho lưu trữ quyền nội bộ có thể ở trạng thái không thể xảy ra trong phiên trình duyệt thực—ví dụ, thông báo đồng thời ở trạng thái denied và prompt. Các script phát hiện thăm dò nhiều quyền và kiểm tra tính nhất quán logic để bắt các tổ hợp trạng thái không hợp lệ này.
Cách plugin Stealth cố gắng (và thường thất bại) che giấu
Các thư viện như puppeteer-extra-plugin-stealth vá nhiều tín hiệu ở trên trước khi bất kỳ script phát hiện nào có thể đọc chúng: chúng ghi đè navigator.webdriver, inject danh sách plugin thực tế, stub các biến toàn cục đặc trưng CDP, và chuẩn hóa phản hồi Permissions API. Với các hệ thống phát hiện đơn giản, cách này hoạt động tốt.
Vấn đề mang tính cấu trúc. Thứ nhất, mỗi bản vá đều là một dấu hiệu tố cáo tiềm tàng: trình bao bọc Object.defineProperty có đầu ra toString() khác với thuộc tính native, và việc thăm dò Object.getOwnPropertyDescriptor(navigator, 'webdriver') tiết lộ một descriptor nhân tạo. Hệ thống phát hiện hiện nay tìm kiếm sự hiện diện của bản vá như một tín hiệu. Thứ hai, plugin stealth hoạt động hoàn toàn ở tầng JavaScript. Chúng không thể chạm tới:
- TLS ClientHello fingerprint — bộ mật mã và tiện ích mở rộng mà trình duyệt cung cấp trong quá trình bắt tay phản ánh thư viện mạng bên dưới (BoringSSL trong Chromium), không phải tầng JavaScript. Chrome headless và Chrome thực chia sẻ cùng TLS fingerprint, nhưng một script Python giả vờ là Chrome thì không.
- Thứ tự frame HTTP/2 — trình tự và mức ưu tiên của các frame HTTP/2 khác nhau giữa trình duyệt thực và automation client xây dựng trên chúng.
- Cây tiến trình hệ điều hành — kiểm tra phía server thấy tiến trình ChromeDriver hay geckodriver bên cạnh tiến trình trình duyệt có bằng chứng out-of-band về automation mà không có bản vá JavaScript nào có thể xóa bỏ.
Kiểm tra thiết lập automation của bạn
Nếu bạn chạy automation headless cho mục đích hợp pháp—pipeline CI, kiểm tra accessibility, theo dõi uptime—bạn có thể thấy chính xác những gì hệ thống phát hiện quan sát về phiên của mình. Công cụ phát hiện bot của BrowserInsight báo cáo trạng thái navigator.webdriver, số lượng plugin, WebGL renderer, và các tín hiệu automation khác theo thời gian thực. Công cụ kernel check tiết lộ engine render thực sự đang chạy bên dưới thông tin user-agent. Hãy dùng chúng để kiểm tra thiết lập trước khi đưa vào production.
Với các tín hiệu ở tầng mạng mà JavaScript không thể thấy, bài viết đi kèm về kỹ thuật phát hiện bot đề cập đến TLS và HTTP/2 fingerprinting.
Câu hỏi thường gặp
Vá navigator.webdriver có đánh lừa được hệ thống phát hiện không?
Chỉ với các kiểm tra đơn giản. Việc thuộc tính vắng mặt là điều bình thường trong trình duyệt thực, nhưng cách nó vắng mặt mới quan trọng. Một giá trị false native và một giá trị false được vá trông khác nhau khi detector đọc property descriptor. Kết hợp vá này với kiểm tra WebGL và plugin thì tín hiệu còn lại đã đủ để phân loại hầu hết automation.
Playwright có thể lách phát hiện tốt hơn Selenium không?
Mặc định của Playwright sạch hơn Selenium cổ điển, và hỗ trợ đa trình duyệt có nghĩa là driver Firefox và WebKit tránh được một số artifact CDP đặc trưng Chrome. Nhưng hệ thống phát hiện theo dõi các tín hiệu đặc trưng Playwright giống như cách chúng theo dõi của Selenium. Hiện chưa có framework nào vô hình hoàn toàn với hệ thống phát hiện kiểm tra toàn bộ tập tín hiệu.
Tại sao Chrome headless báo cáo SwiftShader?
SwiftShader là renderer phần mềm dựa CPU của Chrome—được dùng khi không có GPU thực nào. Server hay container chạy Chrome headless thường không có card màn hình vật lý, vì vậy GPU dự phòng về SwiftShader. Chrome desktop thực báo cáo tên driver GPU thực: Intel, NVIDIA, AMD hay Apple Silicon.
Có thiết lập headless nào qua được mọi kiểm tra không?
Về lý thuyết là có, nhưng thực tế rất khó. Bạn cần: sửa các rò rỉ ở tầng JavaScript (plugin stealth xử lý được hầu hết), cung cấp quyền truy cập GPU thực (sửa WebGL), dùng network stack Chrome thực với TLS đúng (phần lớn ổn với automation dựa Chrome), và tạo ra tiếng ồn hành vi thực tế ở quy mô lớn. Các trình duyệt anti-detect thương mại cố gắng làm điều này; bài viết của chúng tôi về phát hiện trình duyệt anti-detect đề cập đến cách các trang web ứng phó với những công cụ đó.


