Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

selenium-chromedriver-proxy

Run Selenium and ChromeDriver through an authenticated proxy, without an extension. Chrome will not send proxy credentials itself and, since Chrome 142, will not load an extension that sends them either — so this puts a tiny proxy on localhost that attaches your BotProxy credentials on the way out. Same program in Go, Node and Python: pick whichever matches your stack, they behave identically, and none of them has a dependency beyond its standard library.

Selenium / Playwright / curl ──▶ 127.0.0.1:8888 ──▶ x.botproxy.net:8080 ──▶ target
        (no credentials)          (adds them)

What BotProxy is

BotProxy is a rotating proxy service for collecting public data at scale. One endpoint, x.botproxy.net:8080, in front of a pool of datacenter exits across the US, EU, Asia and Australia — you ask for a country or a city in the proxy username, and requests leave from a different IP each time unless you pin one with a session id. Residential exits are on the same account and the same bill for the targets that need them, and Anti-Detect mode gives requests a real browser's TLS and HTTP/2 fingerprint. All locations are included on every plan; you pay for traffic, not per request.

An active subscription is what makes the credentials in this README work — botproxy.com/pricing. If you drive Scrapy rather than a browser, scrapy-botproxy does this job as a downloader middleware, and if you would rather not run a browser at all, the Browser API rents you cloud Chrome workers that are already behind the proxy.

Why this exists

Chrome will not take proxy credentials on the command line — --proxy-server=http://user:pass@host:port silently drops the user:pass part. The workaround everyone used was to generate a small extension that answered the auth challenge from chrome.webRequest.onAuthRequired. Chrome 142 removed the --load-extension switch, along with the --disable-features=DisableLoadExtensionCommandLineSwitch escape hatch that had kept it alive, so that workaround is finished on Chrome-branded builds.

A local hop sidesteps the whole problem. Chrome talks to 127.0.0.1 with no authentication, which it is perfectly happy to do, and the hop attaches the Proxy-Authorization header upstream. Nothing in this arrangement depends on a browser feature, so the next Chrome release cannot take it away either.

You may not need this

If the machine running the browser has a static IP, add it to your proxy user's IP whitelist in the dashboard and skip the hop entirely:

options.add_argument("--proxy-server=http://x.botproxy.net:8080")

No credentials means no auth challenge, nothing to intercept, and one less moving part. The trade-off is that IP-whitelisted requests carry no username, and the username is where BotProxy reads per-request instructions from — so you lose the country, location, residential and session selectors described below. Use the hop when you need those, or when the client IP moves (laptops, CI runners, containers with dynamic egress).

Quick start

Pick one. All three read the same config and take the same flags.

Go — a single static binary, nothing to install:

cd go && go build -o proxy-hop .
./proxy-hop --user pxu1000-0 --pass your-password

Node — 16+, no npm install needed:

node node/proxy-hop.js --user pxu1000-0 --pass your-password

Python — 3.8+, standard library only:

python3 python/proxy_hop.py --user pxu1000-0 --pass your-password

Each prints what it is doing and then waits:

listening on 127.0.0.1:8888
upstream x.botproxy.net:8080 as pxu1000-0

Point anything at http://127.0.0.1:8888 and it goes out through BotProxy:

curl -x http://127.0.0.1:8888 https://httpbin.org/ip

Configuration

Credentials are the only thing you have to supply; the endpoint is already set to x.botproxy.net:8080. Copy proxyhop.example.json to proxyhop.json and fill in the two fields:

{
  "username": "pxu1000-0",
  "password": "your-proxy-user-password"
}

The file is read from the working directory (or --config path.json). Environment variables override it, and flags override those, so one config file can serve several runs that differ in one field.

Config key Flag Environment Default
username --user BOTPROXY_USERNAME — (required)
password --pass BOTPROXY_PASSWORD — (required)
listen --listen 127.0.0.1:8888
upstream --upstream x.botproxy.net:8080
country --country none
location --location none
residential --residential off
session --session none
verbose -v off

proxyhop.json is in .gitignore — it holds a password, and this is a public repo you may well have forked.

Choosing where requests come out

BotProxy takes its instructions from the proxy username rather than from headers. The hop assembles that username for you, so you set intent rather than syntax:

Setting Effect
--country US any exit in that country
--location us-ny a specific location
--residential true residential exit, any region
--residential US residential exit in a country
--session batch-17 keep the same exit IP across requests

If several are given, residential wins over location, and location wins over country. --country RS means residential, not Serbia — for a Serbian datacenter exit ask for --location rs-bg.

The same session id keeps the same exit IP. Sessions expire after your proxy user's max session age (60 seconds by default, configurable in the dashboard) and after 5 minutes of inactivity regardless.

Each hop is one set of selectors. If you need two exit countries at once, run two hops on two ports — they are a few hundred KB of RAM each.

Using it

Selenium (Python) — see examples/selenium_python.py:

options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://127.0.0.1:8888")
driver = webdriver.Chrome(options=options)

Selenium (Node) — see examples/selenium_node.js:

const options = new chrome.Options().addArguments('--proxy-server=http://127.0.0.1:8888');

Playwright:

browser = playwright.chromium.launch(proxy={"server": "http://127.0.0.1:8888"})

Puppeteer:

const browser = await puppeteer.launch({ args: ['--proxy-server=http://127.0.0.1:8888'] });

Anything elseHTTP_PROXY=http://127.0.0.1:8888 HTTPS_PROXY=http://127.0.0.1:8888.

When BotProxy refuses

A refusal is passed through with its own status code rather than flattened into a generic 502, and the hop logs what the code means:

Code Meaning
401 bad proxy credentials
402 subscription suspended, or residential balance exhausted
403 proxy user auto-disabled by the success-rate guard
407 credentials missing or malformed
555 blacklisted site or port, or a free-trial restriction
556 blocked by one of your own request filters

What it does and does not do

  • Listens on localhost, without authentication. Anyone who can open a socket on your machine can spend your traffic. Keep the default bind address unless you know why you are changing it; if you do move it off 127.0.0.1, put it behind something that authenticates.
  • HTTPS is a splice. The credentials go out once, in the CONNECT; everything after that is opaque bytes copied in both directions. The hop never sees inside your TLS and never terminates it, so certificate pinning, HTTP/2 and websockets pass through untouched.
  • Plain http:// requests are rewritten, which is what an HTTP proxy does. The Go and Node hops pool upstream connections for these; the Python hop opens one per request, which is simpler and costs nothing in practice because browsers send everything over CONNECT.
  • No retries, no caching, no rewriting. BotProxy already retries across its own nodes. This is a credential-attaching relay and deliberately nothing else.

Tests

python3 test/smoke.py            # all three implementations
python3 test/smoke.py python go  # a subset

The suite runs each hop against a fake upstream proxy that demands Basic auth, drives it the way a browser does — an ordinary http:// request and a CONNECT tunnel — checks that a rejected credential arrives as the upstream's own status code, and asserts that the upstream saw the exact username the selectors should have produced.

License

MIT. You will need an active BotProxy subscription for the proxy itself — botproxy.com.

About

Use authenticated proxies with Selenium and ChromeDriver without an extension — a tiny local proxy that attaches your BotProxy credentials, in Go, Node and Python.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages