Skip to content

SAKMZ/selenium-proxy-tools

Repository files navigation

selenium-proxy-tools

tests PyPI Python License: MIT

Authenticated & rotating proxies made easy for Chrome-based browser automation — Selenium, SeleniumBase, undetected-chromedriver, Playwright, or plain requests.

Zero third-party dependencies. Standard library only.


Why?

Proxies are a constant source of pain in browser automation:

Problem Chrome / Selenium out of the box With selenium-proxy-tools
Authenticated SOCKS5/SOCKS4 ❌ impossible — Chrome can't auth SOCKS at all
Authenticated HTTP/HTTPS ⚠️ needs a hand-built MV3 extension per session
Rotating pool / round-robin ❌ roll your own ✅ built in
DNS leaks ⚠️ ✅ remote DNS via SOCKS5

It works by running a tiny local, no-auth HTTP proxy on a background thread that forwards every connection through your real upstream proxy. You just point the browser at the local address — no flags, no extensions, no external tools.

Chrome ──▶ 127.0.0.1:<port>  (local, no auth)
                 │
                 ▼
   your authenticated / rotating upstream proxy  ──▶  the internet

Install

pip install selenium-proxy-tools

Quick start

SeleniumBase

from seleniumbase import SB
from selenium_proxy_tools import ProxyRelay

with ProxyRelay("socks5://user:pass@host:1080") as relay:
    # relay.proxy -> "127.0.0.1:<port>"  (exactly what SeleniumBase expects)
    with SB(uc=True, proxy=relay.proxy) as sb:
        sb.open("https://api.ipify.org?format=json")
        print(sb.get_text("body"))

undetected-chromedriver / plain Selenium

import undetected_chromedriver as uc
from selenium_proxy_tools import ProxyRelay

# Works the same for an authenticated HTTP proxy: "http://user:pass@host:8080"
with ProxyRelay("socks5://user:pass@host:1080") as relay:
    options = uc.ChromeOptions()
    options.add_argument(f"--proxy-server={relay.url}")   # http://127.0.0.1:<port>
    driver = uc.Chrome(options=options)
    driver.get("https://api.ipify.org?format=json")
    print(driver.find_element("tag name", "body").text)
    driver.quit()

Rotating pool

from selenium_proxy_tools import ProxyRelay

pool = [
    "socks5://user:pass@a.example.com:1080",
    "http://user:pass@b.example.com:8080",     # mix schemes freely
    "socks5://user:pass@c.example.com:1080",
]

with ProxyRelay(pool, rotation="roundrobin") as relay:   # or rotation="random"
    with SB(uc=True, proxy=relay.proxy) as sb:
        ...   # each new connection exits through the next upstream

requests / verify the exit IP

import requests
from selenium_proxy_tools import ProxyRelay

with ProxyRelay("http://user:pass@host:8080") as relay:
    print(requests.get("https://api.ipify.org", proxies={"https": relay.url}).text)
    print("exit IP:", relay.exit_ip())

More runnable examples in examples/.

Supported upstreams

Scheme Auth Notes
socks5:// ✅ user/pass (RFC 1929) remote DNS (no leaks)
socks4:// ✅ userid socks4a remote DNS
http:// / https:// ✅ Basic CONNECT + absolute-form

Accepted formats: scheme://user:pass@host:port, scheme://host:port, host:port:user:pass (assumes SOCKS5), host:port.

API

ProxyRelay(upstream, listen_host="127.0.0.1", listen_port=0, rotation=None)

upstream is one proxy or a list of them. rotation is None, "roundrobin", or "random".

Member Description
.start(timeout=10.0) Start; blocks until bound. Returns self.
.stop() Stop and release the local port.
.proxy "127.0.0.1:<port>" — for SeleniumBase / uc proxy=.
.url "http://127.0.0.1:<port>" — for --proxy-server=, requests, Playwright.
.port The chosen local port (None until started).
.exit_ip(timeout=20) The public IP currently seen through the relay.
context manager with ProxyRelay(...) as relay: starts on enter, stops on exit.

SocksProxyRelay is a backwards-compatible alias of ProxyRelay.

Also exported: parse_upstream(str) -> UpstreamProxy and get_exit_ip(proxy, timeout=20).

Manual lifecycle (reuse one relay across many browsers):

relay = ProxyRelay("socks5://user:pass@host:1080").start()
try:
    ...   # use relay.proxy / relay.url as many times as you like
finally:
    relay.stop()

How it works

  • Binds a local asyncio TCP server on 127.0.0.1 (ephemeral port by default), running its own event loop on a daemon thread — so it plays nicely inside ordinary synchronous Selenium code.
  • Speaks the HTTP proxy protocol locally: CONNECT for HTTPS tunnels, absolute-form for plain HTTP.
  • Opens each connection through the selected upstream: SOCKS5 (user/pass), SOCKS4/4a, or HTTP/HTTPS (CONNECT + Proxy-Authorization), then pipes bytes both ways.

FAQ

Does the local proxy need a password? No — it listens on loopback only and isn't reachable from outside your machine.

HTTP and HTTPS both work? Yes. HTTPS goes through CONNECT (end-to-end encrypted; the relay only tunnels bytes).

Do I need pysocks, pproxy, or anything else? No — standard library only.

Rotating residential proxies? Either give a pool and use rotation=, or if your provider rotates behind a fixed host:port, just keep one relay — each connection exits via whatever IP the upstream currently serves.

Development

git clone https://github.com/SAKMZ/selenium-proxy-tools
cd selenium-proxy-tools
python -m unittest discover -s tests -v

The suite spins up local SOCKS5-auth and HTTP-auth proxy servers, so the full relay path (auth + rotation) is exercised without touching the network.

License

MIT © Saif Ali (SAKMZ)

About

Authenticated & rotating proxies for Chrome-based Selenium, SeleniumBase, undetected-chromedriver & Playwright. Handles authed SOCKS5/SOCKS4 + HTTP/HTTPS and proxy pools via a local relay.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages