playwright-proxy-authentication 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 JiBao Proxy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: playwright-proxy-authentication
3
+ Version: 0.1.0
4
+ Summary: Authenticated and rotating proxies for Playwright — turn user:pass proxy URLs into Playwright's proxy dict and rotate per context.
5
+ Author-email: JiBao Proxy <support@jibaoproxy.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://jibaoproxy.com
8
+ Project-URL: Source, https://github.com/jibaoproxyofficial-pixel/playwright-proxy-authentication
9
+ Keywords: playwright,proxy,proxy-authentication,rotating-proxy,web-scraping,browser-automation,residential-proxy
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Internet :: WWW/HTTP
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Dynamic: license-file
18
+
19
+ # playwright-proxy-authentication
20
+
21
+ [![PyPI version](https://img.shields.io/pypi/v/playwright-proxy-authentication.svg)](https://pypi.org/project/playwright-proxy-authentication/)
22
+ [![Python versions](https://img.shields.io/pypi/pyversions/playwright-proxy-authentication.svg)](https://pypi.org/project/playwright-proxy-authentication/)
23
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
24
+
25
+ Use **authenticated and rotating proxies with [Playwright](https://playwright.dev)** without fighting its proxy API. Turn an ordinary `scheme://user:pass@host:port` URL into the dict Playwright expects, and hand out a fresh exit IP per browser context.
26
+
27
+ ```bash
28
+ pip install playwright-proxy-authentication
29
+ ```
30
+
31
+ ## The problem
32
+
33
+ Unlike `requests` or Scrapy, Playwright **ignores credentials in a proxy URL**. This silently fails to authenticate:
34
+
35
+ ```python
36
+ # ❌ username/password are dropped — you get 407 Proxy Authentication Required
37
+ browser.new_context(proxy={"server": "http://user:pass@gw.example.com:913"})
38
+ ```
39
+
40
+ Playwright wants the parts split out:
41
+
42
+ ```python
43
+ # ✅ correct
44
+ browser.new_context(proxy={
45
+ "server": "http://gw.example.com:913",
46
+ "username": "user",
47
+ "password": "pass",
48
+ })
49
+ ```
50
+
51
+ This package does that conversion for you, and adds per-context rotation.
52
+
53
+ ## Usage
54
+
55
+ ```python
56
+ from playwright.sync_api import sync_playwright
57
+ from playwright_proxy_auth import ProxyPool
58
+
59
+ # A rotating residential gateway gives a new exit IP on every connection.
60
+ pool = ProxyPool(gateway="http://USERNAME:PASSWORD@us.jibaoproxy.com:913")
61
+
62
+ with sync_playwright() as p:
63
+ browser = p.chromium.launch(headless=True)
64
+ for _ in range(3):
65
+ context = browser.new_context(proxy=pool.get()) # fresh IP per context
66
+ page = context.new_page()
67
+ page.goto("https://httpbin.org/ip")
68
+ print(page.inner_text("body"))
69
+ context.close()
70
+ browser.close()
71
+ ```
72
+
73
+ Static list instead of a gateway:
74
+
75
+ ```python
76
+ pool = ProxyPool(proxies=[
77
+ "http://USERNAME:PASSWORD@proxy-a.example.com:8000",
78
+ "http://USERNAME:PASSWORD@proxy-b.example.com:8000",
79
+ ])
80
+ ```
81
+
82
+ One-off conversion without the pool:
83
+
84
+ ```python
85
+ from playwright_proxy_auth import to_playwright_proxy
86
+ proxy = to_playwright_proxy("http://user:pass@gw.example.com:913")
87
+ browser.new_context(proxy=proxy)
88
+ ```
89
+
90
+ See [`examples/`](examples/) for the async version.
91
+
92
+ ## Gotchas this saves you from
93
+
94
+ - **Credentials in the URL are ignored** — they must be a separate `username`/`password`. (Main reason for it.)
95
+ - **Authenticated SOCKS5 is not supported by Playwright.** A `socks5://user:pass@…` URL raises a clear error instead of failing silently — use an HTTP/HTTPS gateway for authenticated rotation.
96
+ - **Proxy is per *context*, not per page.** Rotate by creating a new `browser.new_context(proxy=...)`; pages inside a context share its exit IP.
97
+
98
+ ## A note on getting blocked anyway
99
+
100
+ A correctly authenticated proxy still gets blocked if the **exit IP is a datacenter range** — anti-bot systems score the ASN and [TLS/JA3 fingerprint](https://jibaoproxy.com/blog/ja3-tls-fingerprint-detection-explained.html) before your page loads. Residential exits with clean ASN reputation are what pass. We build [JiBao Proxy](https://jibaoproxy.com) for this: 72M+ residential IPs across 200+ countries with sticky sessions. This package works with any HTTP proxy provider, though.
101
+
102
+ ## Related
103
+
104
+ - [Selenium & Playwright proxy authentication, explained](https://jibaoproxy.com/blog/selenium-playwright-proxy-authentication.html)
105
+ - [Why your JA3/TLS fingerprint gets you blocked](https://jibaoproxy.com/blog/ja3-tls-fingerprint-detection-explained.html)
106
+ - [browser-use & AI agent proxies](https://jibaoproxy.com/blog/browser-use-ai-agent-proxies.html)
107
+
108
+ ## License
109
+
110
+ MIT
@@ -0,0 +1,92 @@
1
+ # playwright-proxy-authentication
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/playwright-proxy-authentication.svg)](https://pypi.org/project/playwright-proxy-authentication/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/playwright-proxy-authentication.svg)](https://pypi.org/project/playwright-proxy-authentication/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ Use **authenticated and rotating proxies with [Playwright](https://playwright.dev)** without fighting its proxy API. Turn an ordinary `scheme://user:pass@host:port` URL into the dict Playwright expects, and hand out a fresh exit IP per browser context.
8
+
9
+ ```bash
10
+ pip install playwright-proxy-authentication
11
+ ```
12
+
13
+ ## The problem
14
+
15
+ Unlike `requests` or Scrapy, Playwright **ignores credentials in a proxy URL**. This silently fails to authenticate:
16
+
17
+ ```python
18
+ # ❌ username/password are dropped — you get 407 Proxy Authentication Required
19
+ browser.new_context(proxy={"server": "http://user:pass@gw.example.com:913"})
20
+ ```
21
+
22
+ Playwright wants the parts split out:
23
+
24
+ ```python
25
+ # ✅ correct
26
+ browser.new_context(proxy={
27
+ "server": "http://gw.example.com:913",
28
+ "username": "user",
29
+ "password": "pass",
30
+ })
31
+ ```
32
+
33
+ This package does that conversion for you, and adds per-context rotation.
34
+
35
+ ## Usage
36
+
37
+ ```python
38
+ from playwright.sync_api import sync_playwright
39
+ from playwright_proxy_auth import ProxyPool
40
+
41
+ # A rotating residential gateway gives a new exit IP on every connection.
42
+ pool = ProxyPool(gateway="http://USERNAME:PASSWORD@us.jibaoproxy.com:913")
43
+
44
+ with sync_playwright() as p:
45
+ browser = p.chromium.launch(headless=True)
46
+ for _ in range(3):
47
+ context = browser.new_context(proxy=pool.get()) # fresh IP per context
48
+ page = context.new_page()
49
+ page.goto("https://httpbin.org/ip")
50
+ print(page.inner_text("body"))
51
+ context.close()
52
+ browser.close()
53
+ ```
54
+
55
+ Static list instead of a gateway:
56
+
57
+ ```python
58
+ pool = ProxyPool(proxies=[
59
+ "http://USERNAME:PASSWORD@proxy-a.example.com:8000",
60
+ "http://USERNAME:PASSWORD@proxy-b.example.com:8000",
61
+ ])
62
+ ```
63
+
64
+ One-off conversion without the pool:
65
+
66
+ ```python
67
+ from playwright_proxy_auth import to_playwright_proxy
68
+ proxy = to_playwright_proxy("http://user:pass@gw.example.com:913")
69
+ browser.new_context(proxy=proxy)
70
+ ```
71
+
72
+ See [`examples/`](examples/) for the async version.
73
+
74
+ ## Gotchas this saves you from
75
+
76
+ - **Credentials in the URL are ignored** — they must be a separate `username`/`password`. (Main reason for it.)
77
+ - **Authenticated SOCKS5 is not supported by Playwright.** A `socks5://user:pass@…` URL raises a clear error instead of failing silently — use an HTTP/HTTPS gateway for authenticated rotation.
78
+ - **Proxy is per *context*, not per page.** Rotate by creating a new `browser.new_context(proxy=...)`; pages inside a context share its exit IP.
79
+
80
+ ## A note on getting blocked anyway
81
+
82
+ A correctly authenticated proxy still gets blocked if the **exit IP is a datacenter range** — anti-bot systems score the ASN and [TLS/JA3 fingerprint](https://jibaoproxy.com/blog/ja3-tls-fingerprint-detection-explained.html) before your page loads. Residential exits with clean ASN reputation are what pass. We build [JiBao Proxy](https://jibaoproxy.com) for this: 72M+ residential IPs across 200+ countries with sticky sessions. This package works with any HTTP proxy provider, though.
83
+
84
+ ## Related
85
+
86
+ - [Selenium & Playwright proxy authentication, explained](https://jibaoproxy.com/blog/selenium-playwright-proxy-authentication.html)
87
+ - [Why your JA3/TLS fingerprint gets you blocked](https://jibaoproxy.com/blog/ja3-tls-fingerprint-detection-explained.html)
88
+ - [browser-use & AI agent proxies](https://jibaoproxy.com/blog/browser-use-ai-agent-proxies.html)
89
+
90
+ ## License
91
+
92
+ MIT
@@ -0,0 +1,4 @@
1
+ from .proxy import ProxyPool, to_playwright_proxy
2
+
3
+ __all__ = ["ProxyPool", "to_playwright_proxy"]
4
+ __version__ = "0.1.0"
@@ -0,0 +1,68 @@
1
+ """Helpers for using authenticated and rotating proxies with Playwright.
2
+
3
+ Playwright won't accept credentials embedded in a proxy URL the way requests or
4
+ Scrapy do — it needs a dict: ``{"server": ..., "username": ..., "password": ...}``.
5
+ These helpers turn an ordinary ``scheme://user:pass@host:port`` URL into that dict
6
+ and let you hand out a fresh proxy per browser context for rotation.
7
+ """
8
+ from urllib.parse import unquote, urlparse
9
+
10
+ __all__ = ["to_playwright_proxy", "ProxyPool"]
11
+
12
+
13
+ def to_playwright_proxy(url):
14
+ """Convert a ``scheme://user:pass@host:port`` URL to a Playwright proxy dict.
15
+
16
+ >>> to_playwright_proxy("http://user:pass@gw.example.com:913")
17
+ {'server': 'http://gw.example.com:913', 'username': 'user', 'password': 'pass'}
18
+
19
+ Note: Playwright supports authentication for HTTP/HTTPS proxies only.
20
+ SOCKS5 proxies that require a username/password are **not** supported by
21
+ Playwright itself — use an HTTP gateway for authenticated rotation.
22
+ """
23
+ p = urlparse(url)
24
+ if not p.hostname:
25
+ raise ValueError(f"Not a valid proxy URL: {url!r}")
26
+ if p.scheme.startswith("socks") and p.username:
27
+ raise ValueError(
28
+ "Playwright does not support authenticated SOCKS proxies. "
29
+ "Use an HTTP/HTTPS proxy URL instead."
30
+ )
31
+ server = f"{p.scheme}://{p.hostname}"
32
+ if p.port:
33
+ server += f":{p.port}"
34
+ proxy = {"server": server}
35
+ if p.username:
36
+ proxy["username"] = unquote(p.username)
37
+ proxy["password"] = unquote(p.password or "")
38
+ return proxy
39
+
40
+
41
+ class ProxyPool:
42
+ """Hand out Playwright proxy dicts, one per call, for per-context rotation.
43
+
44
+ A rotating residential gateway already returns a new exit IP on every
45
+ connection, so a single gateway URL is enough::
46
+
47
+ pool = ProxyPool(gateway="http://USER:PASS@us.jibaoproxy.com:913")
48
+ context = browser.new_context(proxy=pool.get())
49
+
50
+ Or rotate over a static list::
51
+
52
+ pool = ProxyPool(proxies=["http://u:p@a:8000", "http://u:p@b:8000"])
53
+ """
54
+
55
+ def __init__(self, proxies=None, gateway=None):
56
+ if not proxies and not gateway:
57
+ raise ValueError("Provide either proxies=[...] or gateway=<url>.")
58
+ self._dicts = [to_playwright_proxy(u) for u in (proxies or [])]
59
+ self._gateway = to_playwright_proxy(gateway) if gateway else None
60
+ self._i = 0
61
+
62
+ def get(self):
63
+ """Return the next proxy dict (round-robins a list, or the gateway)."""
64
+ if self._gateway is not None:
65
+ return dict(self._gateway)
66
+ proxy = self._dicts[self._i % len(self._dicts)]
67
+ self._i += 1
68
+ return dict(proxy)
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: playwright-proxy-authentication
3
+ Version: 0.1.0
4
+ Summary: Authenticated and rotating proxies for Playwright — turn user:pass proxy URLs into Playwright's proxy dict and rotate per context.
5
+ Author-email: JiBao Proxy <support@jibaoproxy.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://jibaoproxy.com
8
+ Project-URL: Source, https://github.com/jibaoproxyofficial-pixel/playwright-proxy-authentication
9
+ Keywords: playwright,proxy,proxy-authentication,rotating-proxy,web-scraping,browser-automation,residential-proxy
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Internet :: WWW/HTTP
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Dynamic: license-file
18
+
19
+ # playwright-proxy-authentication
20
+
21
+ [![PyPI version](https://img.shields.io/pypi/v/playwright-proxy-authentication.svg)](https://pypi.org/project/playwright-proxy-authentication/)
22
+ [![Python versions](https://img.shields.io/pypi/pyversions/playwright-proxy-authentication.svg)](https://pypi.org/project/playwright-proxy-authentication/)
23
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
24
+
25
+ Use **authenticated and rotating proxies with [Playwright](https://playwright.dev)** without fighting its proxy API. Turn an ordinary `scheme://user:pass@host:port` URL into the dict Playwright expects, and hand out a fresh exit IP per browser context.
26
+
27
+ ```bash
28
+ pip install playwright-proxy-authentication
29
+ ```
30
+
31
+ ## The problem
32
+
33
+ Unlike `requests` or Scrapy, Playwright **ignores credentials in a proxy URL**. This silently fails to authenticate:
34
+
35
+ ```python
36
+ # ❌ username/password are dropped — you get 407 Proxy Authentication Required
37
+ browser.new_context(proxy={"server": "http://user:pass@gw.example.com:913"})
38
+ ```
39
+
40
+ Playwright wants the parts split out:
41
+
42
+ ```python
43
+ # ✅ correct
44
+ browser.new_context(proxy={
45
+ "server": "http://gw.example.com:913",
46
+ "username": "user",
47
+ "password": "pass",
48
+ })
49
+ ```
50
+
51
+ This package does that conversion for you, and adds per-context rotation.
52
+
53
+ ## Usage
54
+
55
+ ```python
56
+ from playwright.sync_api import sync_playwright
57
+ from playwright_proxy_auth import ProxyPool
58
+
59
+ # A rotating residential gateway gives a new exit IP on every connection.
60
+ pool = ProxyPool(gateway="http://USERNAME:PASSWORD@us.jibaoproxy.com:913")
61
+
62
+ with sync_playwright() as p:
63
+ browser = p.chromium.launch(headless=True)
64
+ for _ in range(3):
65
+ context = browser.new_context(proxy=pool.get()) # fresh IP per context
66
+ page = context.new_page()
67
+ page.goto("https://httpbin.org/ip")
68
+ print(page.inner_text("body"))
69
+ context.close()
70
+ browser.close()
71
+ ```
72
+
73
+ Static list instead of a gateway:
74
+
75
+ ```python
76
+ pool = ProxyPool(proxies=[
77
+ "http://USERNAME:PASSWORD@proxy-a.example.com:8000",
78
+ "http://USERNAME:PASSWORD@proxy-b.example.com:8000",
79
+ ])
80
+ ```
81
+
82
+ One-off conversion without the pool:
83
+
84
+ ```python
85
+ from playwright_proxy_auth import to_playwright_proxy
86
+ proxy = to_playwright_proxy("http://user:pass@gw.example.com:913")
87
+ browser.new_context(proxy=proxy)
88
+ ```
89
+
90
+ See [`examples/`](examples/) for the async version.
91
+
92
+ ## Gotchas this saves you from
93
+
94
+ - **Credentials in the URL are ignored** — they must be a separate `username`/`password`. (Main reason for it.)
95
+ - **Authenticated SOCKS5 is not supported by Playwright.** A `socks5://user:pass@…` URL raises a clear error instead of failing silently — use an HTTP/HTTPS gateway for authenticated rotation.
96
+ - **Proxy is per *context*, not per page.** Rotate by creating a new `browser.new_context(proxy=...)`; pages inside a context share its exit IP.
97
+
98
+ ## A note on getting blocked anyway
99
+
100
+ A correctly authenticated proxy still gets blocked if the **exit IP is a datacenter range** — anti-bot systems score the ASN and [TLS/JA3 fingerprint](https://jibaoproxy.com/blog/ja3-tls-fingerprint-detection-explained.html) before your page loads. Residential exits with clean ASN reputation are what pass. We build [JiBao Proxy](https://jibaoproxy.com) for this: 72M+ residential IPs across 200+ countries with sticky sessions. This package works with any HTTP proxy provider, though.
101
+
102
+ ## Related
103
+
104
+ - [Selenium & Playwright proxy authentication, explained](https://jibaoproxy.com/blog/selenium-playwright-proxy-authentication.html)
105
+ - [Why your JA3/TLS fingerprint gets you blocked](https://jibaoproxy.com/blog/ja3-tls-fingerprint-detection-explained.html)
106
+ - [browser-use & AI agent proxies](https://jibaoproxy.com/blog/browser-use-ai-agent-proxies.html)
107
+
108
+ ## License
109
+
110
+ MIT
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ playwright_proxy_auth/__init__.py
5
+ playwright_proxy_auth/proxy.py
6
+ playwright_proxy_authentication.egg-info/PKG-INFO
7
+ playwright_proxy_authentication.egg-info/SOURCES.txt
8
+ playwright_proxy_authentication.egg-info/dependency_links.txt
9
+ playwright_proxy_authentication.egg-info/top_level.txt
10
+ tests/test_proxy.py
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "playwright-proxy-authentication"
7
+ version = "0.1.0"
8
+ description = "Authenticated and rotating proxies for Playwright — turn user:pass proxy URLs into Playwright's proxy dict and rotate per context."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "JiBao Proxy", email = "support@jibaoproxy.com" }]
13
+ keywords = ["playwright", "proxy", "proxy-authentication", "rotating-proxy", "web-scraping", "browser-automation", "residential-proxy"]
14
+ classifiers = [
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Topic :: Internet :: WWW/HTTP",
19
+ ]
20
+
21
+ [project.urls]
22
+ Homepage = "https://jibaoproxy.com"
23
+ Source = "https://github.com/jibaoproxyofficial-pixel/playwright-proxy-authentication"
24
+
25
+ [tool.setuptools]
26
+ packages = ["playwright_proxy_auth"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,33 @@
1
+ import pytest
2
+ from playwright_proxy_auth import ProxyPool, to_playwright_proxy
3
+
4
+
5
+ def test_auth_url_to_dict():
6
+ assert to_playwright_proxy("http://user:pass@gw.example.com:913") == {
7
+ "server": "http://gw.example.com:913",
8
+ "username": "user",
9
+ "password": "pass",
10
+ }
11
+
12
+
13
+ def test_no_auth_url():
14
+ assert to_playwright_proxy("http://gw.example.com:8000") == {
15
+ "server": "http://gw.example.com:8000"
16
+ }
17
+
18
+
19
+ def test_socks_with_auth_rejected():
20
+ with pytest.raises(ValueError):
21
+ to_playwright_proxy("socks5://user:pass@gw.example.com:1080")
22
+
23
+
24
+ def test_pool_list_round_robins():
25
+ pool = ProxyPool(proxies=["http://u:p@a:8000", "http://u:p@b:8000"])
26
+ assert pool.get()["server"] == "http://a:8000"
27
+ assert pool.get()["server"] == "http://b:8000"
28
+ assert pool.get()["server"] == "http://a:8000"
29
+
30
+
31
+ def test_pool_gateway_constant():
32
+ pool = ProxyPool(gateway="http://u:p@gw:913")
33
+ assert pool.get() == pool.get() == {"server": "http://gw:913", "username": "u", "password": "p"}