Đây là cái bẫy mà mọi lập trình viên tự động hóa trình duyệt đều mắc đúng một lần: bạn thêm --proxy-server=http://user:pass@host:port vào cờ khởi chạy của Chrome, trình duyệt khởi động bình thường, rồi mọi trang đều treo lại ở một popup xác thực mà script của bạn không thể nhìn thấy hay nhấn vào. Chrome âm thầm loại bỏ thông tin đăng nhập khỏi cờ proxy. Không có lỗi, không có dòng log, chỉ là một hộp thoại modal trong một trình duyệt mà chẳng ai để mắt tới.
Hướng dẫn này là bản đồ đầy đủ về các phương pháp xác thực proxy thực sự hoạt động trong Selenium và Playwright năm 2026 — nên dùng cái nào, nên tránh cái nào, và đoạn code chính xác cho từng cách. (Về chiến lược chọn proxy và xoay proxy trong các công cụ này, xem Sử dụng Proxy với Playwright & Puppeteer — bài viết này chỉ tập trung vào việc đưa thông tin đăng nhập đi qua.)
Các HTTP client dòng lệnh phân tích thông tin đăng nhập từ URL proxy và tự động trả lời phản hồi 407 Proxy Authentication Required của proxy bằng một header Proxy-Authorization. Chromium thì không: --proxy-server chỉ chấp nhận scheme://host:port, và khi 407 quay về, nó hiển thị một hộp thoại tương tác. Ở chế độ headless thậm chí còn chẳng có hộp thoại — yêu cầu chỉ đơn giản là thất bại.
Vậy nên câu hỏi không bao giờ là "làm sao đặt mật khẩu vào URL" — mà là "tầng nào sẽ trả lời 407 thay tôi". Có bốn câu trả lời hoạt động được, tốt nhất xếp trước.
Playwright đã giải quyết vấn đề một cách đàng hoàng: nó tự trả lời các thử thách xác thực proxy qua CDP. Thông tin đăng nhập đặt trong tùy chọn launch hoặc context:
# Python
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
proxy={
"server": "http://us.jibaoproxy.com:913",
"username": "USERNAME-session-job1",
"password": "PASSWORD",
}
)
// Node.js - và theo từng context, đây là nơi nó trở nên mạnh mẽ:
const browser = await chromium.launch({
proxy: { server: 'per-context' } // khai báo ý định khi launch
});
const ctx = await browser.newContext({
proxy: {
server: 'http://us.jibaoproxy.com:913',
username: 'USERNAME-session-job2',
password: 'PASSWORD',
}
});
Proxy theo từng context nghĩa là một tiến trình trình duyệt có thể chạy N context trên N phiên sticky khác nhau — nền tảng của mọi thiết lập scraping nghiêm túc bằng Playwright. Nếu bạn đang dùng Playwright, vậy là xong; nhảy thẳng tới phần xác minh. Mọi thứ bên dưới tồn tại là vì Selenium không có cơ chế tương đương.
selenium-wire bọc Selenium bằng một proxy MITM cục bộ xử lý xác thực với upstream:
from seleniumwire import webdriver # pip install selenium-wire
options = {
"proxy": {
"http": "http://USERNAME:[email protected]:913",
"https": "https://USERNAME:[email protected]:913",
}
}
driver = webdriver.Chrome(seleniumwire_options=options)
driver.get("https://example.com")
Những lưu ý bạn nên biết trước khi đưa lên production:
Cách tiếp cận kinh điển: một tiện ích nhỏ được sinh ra để thiết lập proxy và trả lời xác thực qua chrome.webRequest.onAuthRequired. Không MITM, không tiến trình phụ, hoạt động trong Selenium thuần:
import zipfile, json
HOST, PORT = "us.jibaoproxy.com", 913
USER, PASS = "USERNAME-session-sel1", "PASSWORD"
manifest = {
"version": "1.0.0", "manifest_version": 2, "name": "Proxy Auth",
"permissions": ["proxy", "webRequest", "webRequestBlocking", ""],
"background": {"scripts": ["background.js"]},
}
background = f"""
chrome.proxy.settings.set({{value: {{mode: "fixed_servers", rules: {{
singleProxy: {{scheme: "http", host: "{HOST}", port: {PORT}}}
}}}}, scope: "regular"}}, () => {{}});
chrome.webRequest.onAuthRequired.addListener(
() => ({{authCredentials: {{username: "{USER}", password: "{PASS}"}}}}),
{{urls: [""]}}, ["blocking"]
);
"""
with zipfile.ZipFile("proxy_auth.zip", "w") as zp:
zp.writestr("manifest.json", json.dumps(manifest))
zp.writestr("background.js", background)
from selenium import webdriver
opts = webdriver.ChromeOptions()
opts.add_extension("proxy_auth.zip")
driver = webdriver.Chrome(options=opts)
Hai điều dễ vấp: tiện ích không tải được ở chế độ headless cũ — hãy dùng --headless=new (Chrome 109+) hoặc chạy có giao diện; và các script nền Manifest V2 vẫn hoạt động với các tiện ích tự động hóa được nạp ngoài (sideload) nhưng hãy để ý mốc thời gian Chrome ngừng hỗ trợ MV2 — phiên bản MV3 tương đương cần một service worker và cùng listener onAuthRequired.
Chạy một proxy cục bộ nhỏ giữ thông tin đăng nhập; trỏ trình duyệt tới localhost hoàn toàn không cần xác thực. Hoạt động với mọi trình duyệt, mọi driver, mọi ngôn ngữ, headless hay không:
# Dùng pproxy (pip install pproxy) - terminal 1:
pproxy -l http://127.0.0.1:8899 \
-r "http://USERNAME:[email protected]:913"
# Script của bạn - terminal 2: Selenium thuần, không cần xác thực
opts = webdriver.ChromeOptions()
opts.add_argument("--proxy-server=http://127.0.0.1:8899")
driver = webdriver.Chrome(options=opts)
Đây cũng là câu trả lời gọn gàng nhất cho Firefox/geckodriver (nơi mẹo tiện ích không áp dụng được) và cho những môi trường đặc thù như kiểm thử ứng dụng Electron. Cái giá phải trả: thêm một bộ phận cần giám sát, và việc định tuyến theo từng context đòi hỏi một cổng forwarder cho mỗi phiên.
| Phương pháp | Hoạt động trong | Headless | Phiên theo từng context | Kết luận cho production |
|---|---|---|---|---|
| Playwright gốc | Playwright | Có | Có | Lựa chọn mặc định |
| selenium-wire | Selenium (Py) | Có | Theo từng driver | Ổn ở quy mô vừa phải |
| Tiện ích xác thực | Selenium (mọi ngôn ngữ) | Chỉ --headless=new | Theo từng driver | Vững chắc, để ý MV3 |
| Bộ chuyển tiếp cục bộ | Mọi thứ | Có | Một cổng cho mỗi phiên | Dự phòng vạn năng |
Âm thầm rơi về IP thật của bạn chính là kiểu thất bại khiến tài khoản bị cấm. Hãy khẳng định IP đầu ra ngay đầu mỗi lần chạy:
ip = driver.execute_script(
"return fetch('https://api.ipify.org').then(r => r.text())"
) if hasattr(driver, 'execute_script') else page.evaluate(
"fetch('https://api.ipify.org').then(r => r.text())"
)
assert ip != MY_REAL_IP, "Proxy not applied - aborting before we leak"
--proxy-server; Chrome đã loại bỏ chúng. Hãy dùng một trong bốn phương pháp.ERR_PROXY_CONNECTION_FAILED — host/port sai hoặc giao thức không khớp. Hãy thử cùng một dòng đó bằng curl -x trước; nếu curl hoạt động thì vấn đề nằm ở cách đấu nối trình duyệt của bạn.--headless=new.Trình duyệt không đọc URL proxy dạng user:pass@ — phải có thứ gì đó trả lời 407 thay chúng. Trong Playwright, thứ đó được tích hợp sẵn; trong Selenium, đó là selenium-wire, một tiện ích xác thực được sinh ra, hoặc một bộ chuyển tiếp cục bộ. Hãy chọn theo ngăn xếp công nghệ của bạn từ bảng so sánh, rồi khẳng định IP đầu ra ở mỗi lần chạy để một lỗi xác thực âm thầm không bao giờ có thể làm lộ địa chỉ thật của bạn giữa chừng quá trình thu thập dữ liệu.
500MB lưu lượng residential miễn phí — một gateway, sticky hoặc xoay, HTTP và SOCKS5.
Bắt đầu dùng thử miễn phíNgười dùng mới nhận 500MB khi đăng ký, cùng tiền thưởng cho lần nạp đầu tiên. Ưu đãi có thời hạn.