Created
August 21, 2026 12:42
-
-
Save quantumproxies/4d9d41bebc444cebeb74a68fee1cf9f0 to your computer and use it in GitHub Desktop.
Selenium + Chrome proxy authentication that actually works — a runtime MV3 extension https://quanticdata.io/blog/how-to-rotate-proxy-in-selenium-python/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """Authenticated proxies in Selenium + Chrome, without selenium-wire. | |
| Chrome's --proxy-server flag accepts host:port only. Credentials in the URL are | |
| ignored and you get a 407. The portable fix is a tiny MV3 extension generated at | |
| runtime that answers the auth challenge — no extra dependency, works headless. | |
| pip install selenium | |
| export QD_PROXY_USER=... QD_PROXY_PASS=... | |
| python3 qd_selenium_proxy.py us | |
| https://quanticdata.io/residential-proxies/ | |
| Guide: https://quanticdata.io/blog/how-to-rotate-proxy-in-selenium-python/ | |
| """ | |
| import json | |
| import os | |
| import sys | |
| import tempfile | |
| import zipfile | |
| from selenium import webdriver | |
| HOST, PORT = "pr.quanticdata.io", 7777 | |
| USER = os.environ["QD_PROXY_USER"] | |
| PASS = os.environ["QD_PROXY_PASS"] | |
| COUNTRY = sys.argv[1] if len(sys.argv) > 1 else "us" | |
| SESSION = sys.argv[2] if len(sys.argv) > 2 else None | |
| username = "-".join([USER, "country", COUNTRY] + (["session", SESSION, "sessTime", "10"] if SESSION else [])) | |
| MANIFEST = { | |
| "manifest_version": 3, | |
| "name": "proxy-auth", | |
| "version": "1.0", | |
| "permissions": ["proxy", "webRequest", "webRequestAuthProvider"], | |
| "host_permissions": ["<all_urls>"], | |
| "background": {"service_worker": "background.js"}, | |
| } | |
| BACKGROUND = f""" | |
| chrome.proxy.settings.set({{ | |
| value: {{ | |
| mode: "fixed_servers", | |
| rules: {{ singleProxy: {{ scheme: "http", host: "{HOST}", port: {PORT} }}, | |
| bypassList: ["localhost"] }} | |
| }}, | |
| scope: "regular" | |
| }}); | |
| chrome.webRequest.onAuthRequired.addListener( | |
| () => ({{ authCredentials: {{ username: {json.dumps(username)}, | |
| password: {json.dumps(PASS)} }} }}), | |
| {{ urls: ["<all_urls>"] }}, | |
| ["blocking"] | |
| ); | |
| """ | |
| path = tempfile.mktemp(suffix=".zip") | |
| with zipfile.ZipFile(path, "w") as z: | |
| z.writestr("manifest.json", json.dumps(MANIFEST)) | |
| z.writestr("background.js", BACKGROUND) | |
| options = webdriver.ChromeOptions() | |
| options.add_extension(path) | |
| # Headless "new" supports MV3 extensions; the old headless mode does not. | |
| options.add_argument("--headless=new") | |
| driver = webdriver.Chrome(options=options) | |
| try: | |
| driver.get("https://ipinfo.io/json") | |
| print(f"country={COUNTRY}{f' session={SESSION}' if SESSION else ' (rotating)'}") | |
| print(driver.find_element("tag name", "body").text) | |
| finally: | |
| driver.quit() | |
| os.unlink(path) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment