proxyproviders 0.2.2__tar.gz → 0.3.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.
- proxyproviders-0.3.0/.gitignore +10 -0
- proxyproviders-0.3.0/PKG-INFO +504 -0
- proxyproviders-0.3.0/README.md +473 -0
- proxyproviders-0.3.0/proxyproviders/__init__.py +31 -0
- {proxyproviders-0.2.2 → proxyproviders-0.3.0}/proxyproviders/algorithms.py +2 -2
- {proxyproviders-0.2.2 → proxyproviders-0.3.0}/proxyproviders/exceptions.py +2 -4
- proxyproviders-0.3.0/proxyproviders/httpx_transport.py +248 -0
- proxyproviders-0.3.0/proxyproviders/models/formats.py +277 -0
- proxyproviders-0.3.0/proxyproviders/models/proxy.py +205 -0
- {proxyproviders-0.2.2 → proxyproviders-0.3.0}/proxyproviders/providers/brightdata.py +25 -17
- proxyproviders-0.3.0/proxyproviders/providers/proxy_list.py +72 -0
- {proxyproviders-0.2.2 → proxyproviders-0.3.0}/proxyproviders/providers/webshare.py +11 -8
- proxyproviders-0.3.0/proxyproviders/proxy_provider.py +252 -0
- proxyproviders-0.3.0/proxyproviders/py.typed +0 -0
- proxyproviders-0.3.0/proxyproviders/session.py +228 -0
- {proxyproviders-0.2.2 → proxyproviders-0.3.0}/proxyproviders/version.py +1 -1
- proxyproviders-0.3.0/pyproject.toml +108 -0
- proxyproviders-0.2.2/PKG-INFO +0 -299
- proxyproviders-0.2.2/README.md +0 -254
- proxyproviders-0.2.2/proxyproviders/__init__.py +0 -16
- proxyproviders-0.2.2/proxyproviders/models/proxy.py +0 -149
- proxyproviders-0.2.2/proxyproviders/proxy_provider.py +0 -130
- proxyproviders-0.2.2/pyproject.toml +0 -49
- {proxyproviders-0.2.2 → proxyproviders-0.3.0}/LICENSE +0 -0
- {proxyproviders-0.2.2 → proxyproviders-0.3.0}/proxyproviders/models/__init__.py +0 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: proxyproviders
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: One interface for proxy vendors (Webshare, Bright Data, or your own list) that works with requests, httpx, aiohttp, Playwright, Scrapy and more
|
|
5
|
+
Project-URL: Homepage, https://github.com/davidteather/proxyproviders
|
|
6
|
+
Project-URL: Documentation, https://davidteather.github.io/proxyproviders/
|
|
7
|
+
Project-URL: Source Code, https://github.com/davidteather/proxyproviders
|
|
8
|
+
Project-URL: Issues, https://github.com/davidteather/proxyproviders/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/davidteather/proxyproviders/releases
|
|
10
|
+
Project-URL: Funding, https://github.com/sponsors/davidteather
|
|
11
|
+
Author-email: David Teather <contact.davidteather@gmail.com>
|
|
12
|
+
License-Expression: MIT
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Keywords: aiohttp,brightdata,httpx,playwright,proxies,proxy,proxy-pool,proxy-provider,proxy-rotation,requests,rotating-proxies,scrapy,socks5,web-scraping,webshare
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: requests
|
|
26
|
+
Provides-Extra: httpx
|
|
27
|
+
Requires-Dist: httpx>=0.26; extra == 'httpx'
|
|
28
|
+
Provides-Extra: socks
|
|
29
|
+
Requires-Dist: requests[socks]; extra == 'socks'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# ProxyProviders
|
|
33
|
+
|
|
34
|
+
One interface for proxy vendors, many Python clients.
|
|
35
|
+
|
|
36
|
+
Get proxies from Webshare, Bright Data or your own list, and use them with requests, httpx, aiohttp, Playwright, Scrapy, Crawlee or curl without writing vendor-specific code. Swap providers by changing one line.
|
|
37
|
+
|
|
38
|
+
[](https://codecov.io/gh/davidteather/proxyproviders) [](https://go.dteather.com/github-sponsors?src=proxyproviders&placement=badge) [](https://github.com/davidteather/proxyproviders/releases) [](https://github.com/davidteather/proxyproviders/blob/main/LICENSE) [](https://pypi.org/project/proxyproviders/)  [](https://go.dteather.com/discord?src=proxyproviders&placement=badge) [](https://www.linkedin.com/in/davidteather/)
|
|
39
|
+
|
|
40
|
+
```py
|
|
41
|
+
import requests
|
|
42
|
+
from proxyproviders import Webshare, ProxyFormat
|
|
43
|
+
|
|
44
|
+
provider = Webshare(api_key="your-api-key") # or BrightData(...), or ProxyList([...])
|
|
45
|
+
proxy = provider.get_proxy()
|
|
46
|
+
|
|
47
|
+
response = requests.get(
|
|
48
|
+
"https://httpbin.org/ip", proxies=proxy.format(ProxyFormat.REQUESTS)
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Table of Contents
|
|
53
|
+
|
|
54
|
+
- [Installation](#installation)
|
|
55
|
+
- [What It Does](#what-it-does)
|
|
56
|
+
- [Works With](#works-with)
|
|
57
|
+
- [Supported Proxy Providers](#supported-proxy-providers)
|
|
58
|
+
- [Quick Start Guide](#quick-start-guide)
|
|
59
|
+
- [Using Proxies With Your HTTP Client](#using-proxies-with-your-http-client)
|
|
60
|
+
- [requests](#python-requests-with-rotating-proxies)
|
|
61
|
+
- [httpx](#httpx-proxy)
|
|
62
|
+
- [aiohttp](#aiohttp-proxy)
|
|
63
|
+
- [Playwright](#playwright-proxy-in-python)
|
|
64
|
+
- [Scrapy](#scrapy-proxy)
|
|
65
|
+
- [Crawlee](#crawlee-for-python-proxy)
|
|
66
|
+
- [curl and subprocesses](#curl-and-subprocesses)
|
|
67
|
+
- [Use Your Own Proxy List](#use-your-own-proxy-list)
|
|
68
|
+
- [Automatic Rotation and Retries](#automatic-rotation-and-retries)
|
|
69
|
+
- [Reporting Failed Proxies](#reporting-failed-proxies)
|
|
70
|
+
- [Choosing Which Proxy You Get](#choosing-which-proxy-you-get)
|
|
71
|
+
- [Configuration](#configuration)
|
|
72
|
+
- [SOCKS Proxies](#socks-proxies)
|
|
73
|
+
- [Making Your Own Proxy Provider](#making-your-own-proxy-provider)
|
|
74
|
+
- [How to Support The Project](#how-to-support-the-project)
|
|
75
|
+
|
|
76
|
+
## Installation
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
python -m pip install proxyproviders
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Python 3.10 or newer. The full documentation is [here](https://davidteather.github.io/proxyproviders), and there are runnable [examples in the repository](https://github.com/davidteather/proxyproviders/tree/main/examples).
|
|
83
|
+
|
|
84
|
+
Using an AI coding assistant? Point it at [llms.txt](https://davidteather.github.io/proxyproviders/llms.txt), a short summary of the API written for assistants.
|
|
85
|
+
|
|
86
|
+
**📺 Video Tutorial:** [Watch the quick start guide on YouTube](https://youtu.be/h-JouO_orYo) for a walkthrough of getting started with ProxyProviders.
|
|
87
|
+
|
|
88
|
+
## What It Does
|
|
89
|
+
|
|
90
|
+
Every proxy vendor has its own API, and every HTTP client wants its proxy in a different shape. ProxyProviders sits between the two:
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
Webshare | Bright Data | your own list
|
|
94
|
+
↓
|
|
95
|
+
ProxyProvider → Proxy
|
|
96
|
+
↓
|
|
97
|
+
requests | httpx | aiohttp | Playwright | Scrapy | Crawlee | curl
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- **Provider API handled for you.** Proxies are fetched from the vendor, cached, and refreshed on an interval.
|
|
101
|
+
- **One `Proxy` model.** Host, port, credentials, country and protocols look the same whichever vendor they came from.
|
|
102
|
+
- **One line to hand it to your client.** `proxy.format(...)` produces what each client expects, with credentials percent-encoded correctly.
|
|
103
|
+
- **Vendor-neutral code.** Anything written against `ProxyProvider` works with every provider, so switching vendor does not touch the rest of your application.
|
|
104
|
+
|
|
105
|
+
Rotation and retries are available if you want them ([`RotatingSession`](#automatic-rotation-and-retries)), and stay out of the way if your framework already does that.
|
|
106
|
+
|
|
107
|
+
## Works With
|
|
108
|
+
|
|
109
|
+
| Client | How to pass the proxy | Format |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| [requests](#python-requests-with-rotating-proxies) | `requests.get(url, proxies=...)` | `ProxyFormat.REQUESTS` |
|
|
112
|
+
| [httpx](#httpx-proxy) | `httpx.Client(proxy=...)` | `ProxyFormat.HTTPX` |
|
|
113
|
+
| [aiohttp](#aiohttp-proxy) | `session.get(url, proxy=...)` | `ProxyFormat.AIOHTTP` |
|
|
114
|
+
| [Playwright](#playwright-proxy-in-python) | `browser.new_context(proxy=...)` | `ProxyFormat.PLAYWRIGHT` |
|
|
115
|
+
| [Scrapy](#scrapy-proxy) | `Request(meta={"proxy": ...})` | `ProxyFormat.SCRAPY` |
|
|
116
|
+
| [Crawlee](#crawlee-for-python-proxy) | `ProxyConfiguration(proxy_urls=[...])` | `proxy.to_url()` |
|
|
117
|
+
| urllib | `urllib.request.ProxyHandler(...)` | `ProxyFormat.URLLIB` |
|
|
118
|
+
| [curl](#curl-and-subprocesses) | `curl -x ...` | `ProxyFormat.CURL` |
|
|
119
|
+
| [Any subprocess](#curl-and-subprocesses) | `HTTP_PROXY` / `HTTPS_PROXY` variables | `ProxyFormat.ENV` |
|
|
120
|
+
| Other tools | `host:port:username:password` | `ProxyFormat.HOST_PORT_USER_PASS` |
|
|
121
|
+
| Anything else | `scheme://username:password@host:port` | `ProxyFormat.URL` |
|
|
122
|
+
|
|
123
|
+
requests, httpx, aiohttp, urllib and curl are tested in CI by sending a real request through a local proxy with each client's current release.
|
|
124
|
+
|
|
125
|
+
## Supported Proxy Providers
|
|
126
|
+
|
|
127
|
+
| Provider | Class | Notes |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| Webshare | `Webshare(api_key=...)` | 10 free datacenter proxies to try it out |
|
|
130
|
+
| Bright Data | `BrightData(api_key=..., zone=...)` | Super proxy or one proxy per IP in the zone |
|
|
131
|
+
| Any other vendor | `ProxyList([...])` | [Your own list](#use-your-own-proxy-list) of proxy URLs or `host:port:user:pass` lines |
|
|
132
|
+
|
|
133
|
+
Getting credentials:
|
|
134
|
+
|
|
135
|
+
- **Webshare:** create an account [here](https://go.dteather.com/webshare?src=proxyproviders&placement=readme) (affiliate link), then generate a token in the [API tab](https://dashboard.webshare.io/userapi/keys).
|
|
136
|
+
- **Bright Data:** create an account [here](https://go.dteather.com/brightdata?src=proxyproviders&placement=readme) (affiliate link), then create an API token in the [account settings](https://brightdata.com/cp/setting/users).
|
|
137
|
+
|
|
138
|
+
Keep API tokens out of your code and repository; the examples below read them from environment variables. Each provider's options are in the [provider documentation](https://davidteather.github.io/proxyproviders/#proxyproviders-supported-providers). Don't see your vendor? [Add a provider](#making-your-own-proxy-provider) or open an issue.
|
|
139
|
+
|
|
140
|
+
## Quick Start Guide
|
|
141
|
+
|
|
142
|
+
List the proxies from a provider:
|
|
143
|
+
|
|
144
|
+
```py
|
|
145
|
+
import os
|
|
146
|
+
from proxyproviders import Webshare
|
|
147
|
+
|
|
148
|
+
provider = Webshare(api_key=os.environ["WEBSHARE_API_KEY"])
|
|
149
|
+
|
|
150
|
+
proxies = provider.list_proxies()
|
|
151
|
+
print(proxies) # passwords are left out of the printed output
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Or take one at a time. `get_proxy()` cycles through the list in order by default:
|
|
155
|
+
|
|
156
|
+
```py
|
|
157
|
+
proxy = provider.get_proxy()
|
|
158
|
+
print(proxy.proxy_address, proxy.port, proxy.country_code)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Providers take their own options. `Webshare` passes `search_params` to the [Webshare API](https://apidocs.webshare.io/proxy-list/list#parameters), so this returns only proxies based in the US:
|
|
162
|
+
|
|
163
|
+
```py
|
|
164
|
+
provider = Webshare(
|
|
165
|
+
api_key=os.environ["WEBSHARE_API_KEY"], search_params={"country_code_in": "US"}
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Because every provider has the same interface, code written against `ProxyProvider` does not care which vendor is behind it:
|
|
170
|
+
|
|
171
|
+
```py
|
|
172
|
+
import os
|
|
173
|
+
import requests
|
|
174
|
+
from proxyproviders import BrightData, ProxyFormat, ProxyProvider, Webshare
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def fetch_ip(provider: ProxyProvider) -> str:
|
|
178
|
+
proxy = provider.get_proxy()
|
|
179
|
+
response = requests.get(
|
|
180
|
+
"https://httpbin.org/ip", proxies=proxy.format(ProxyFormat.REQUESTS)
|
|
181
|
+
)
|
|
182
|
+
return response.json()["origin"]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
fetch_ip(Webshare(api_key=os.environ["WEBSHARE_API_KEY"]))
|
|
186
|
+
fetch_ip(BrightData(api_key=os.environ["BRIGHTDATA_API_KEY"], zone="my_zone"))
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
That is how other libraries, such as my [TikTokAPI](https://go.dteather.com/tiktokapi?src=proxyproviders&placement=readme) package, accept any proxy vendor without knowing about it.
|
|
190
|
+
|
|
191
|
+
## Using Proxies With Your HTTP Client
|
|
192
|
+
|
|
193
|
+
Every example below starts with a `provider`, which can be any provider from the table above.
|
|
194
|
+
|
|
195
|
+
### Python requests With Rotating Proxies
|
|
196
|
+
|
|
197
|
+
```py
|
|
198
|
+
import requests
|
|
199
|
+
from proxyproviders import ProxyFormat
|
|
200
|
+
|
|
201
|
+
proxy = provider.get_proxy()
|
|
202
|
+
response = requests.get(
|
|
203
|
+
"https://httpbin.org/ip", proxies=proxy.format(ProxyFormat.REQUESTS)
|
|
204
|
+
)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Each `get_proxy()` call returns the next proxy, so calling it per request rotates through your proxies. To have rotation and retries done for you, use [`RotatingSession`](#automatic-rotation-and-retries).
|
|
208
|
+
|
|
209
|
+
### httpx Proxy
|
|
210
|
+
|
|
211
|
+
httpx (0.26 or newer) takes one proxy URL in its `proxy=` argument, for both the sync and async clients:
|
|
212
|
+
|
|
213
|
+
```py
|
|
214
|
+
import httpx
|
|
215
|
+
from proxyproviders import ProxyFormat
|
|
216
|
+
|
|
217
|
+
with httpx.Client(proxy=provider.get_proxy().format(ProxyFormat.HTTPX)) as client:
|
|
218
|
+
response = client.get("https://httpbin.org/ip")
|
|
219
|
+
|
|
220
|
+
async with httpx.AsyncClient(
|
|
221
|
+
proxy=provider.get_proxy().format(ProxyFormat.HTTPX)
|
|
222
|
+
) as client:
|
|
223
|
+
response = await client.get("https://httpbin.org/ip")
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### aiohttp Proxy
|
|
227
|
+
|
|
228
|
+
```py
|
|
229
|
+
import aiohttp
|
|
230
|
+
from proxyproviders import ProxyFormat
|
|
231
|
+
|
|
232
|
+
async with aiohttp.ClientSession() as session:
|
|
233
|
+
async with session.get(
|
|
234
|
+
"https://httpbin.org/ip", proxy=provider.get_proxy().format(ProxyFormat.AIOHTTP)
|
|
235
|
+
) as response:
|
|
236
|
+
print(await response.json())
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Playwright Proxy in Python
|
|
240
|
+
|
|
241
|
+
```py
|
|
242
|
+
from playwright.sync_api import sync_playwright
|
|
243
|
+
from proxyproviders import ProxyFormat
|
|
244
|
+
|
|
245
|
+
with sync_playwright() as p:
|
|
246
|
+
browser = p.chromium.launch()
|
|
247
|
+
context = browser.new_context(
|
|
248
|
+
proxy=provider.get_proxy().format(ProxyFormat.PLAYWRIGHT)
|
|
249
|
+
)
|
|
250
|
+
page = context.new_page()
|
|
251
|
+
page.goto("https://httpbin.org/ip")
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Give each browser context its own `get_proxy()` call to spread contexts across proxies. The same dictionary works for `p.chromium.launch(proxy=...)`.
|
|
255
|
+
|
|
256
|
+
### Scrapy Proxy
|
|
257
|
+
|
|
258
|
+
Scrapy's built-in `HttpProxyMiddleware` reads the proxy from `request.meta` and takes the credentials out of the URL:
|
|
259
|
+
|
|
260
|
+
```py
|
|
261
|
+
import scrapy
|
|
262
|
+
from proxyproviders import ProxyFormat
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class IpSpider(scrapy.Spider):
|
|
266
|
+
name = "ip"
|
|
267
|
+
|
|
268
|
+
def start_requests(self):
|
|
269
|
+
yield scrapy.Request(
|
|
270
|
+
"https://httpbin.org/ip",
|
|
271
|
+
meta={"proxy": provider.get_proxy().format(ProxyFormat.SCRAPY)},
|
|
272
|
+
)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Crawlee for Python Proxy
|
|
276
|
+
|
|
277
|
+
Crawlee rotates proxies and manages sessions itself, so hand it the whole list:
|
|
278
|
+
|
|
279
|
+
```py
|
|
280
|
+
from crawlee.proxy_configuration import ProxyConfiguration
|
|
281
|
+
|
|
282
|
+
proxy_configuration = ProxyConfiguration(
|
|
283
|
+
proxy_urls=[proxy.to_url() for proxy in provider.list_proxies()]
|
|
284
|
+
)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### curl and Subprocesses
|
|
288
|
+
|
|
289
|
+
```py
|
|
290
|
+
import os
|
|
291
|
+
import subprocess
|
|
292
|
+
from proxyproviders import ProxyFormat
|
|
293
|
+
|
|
294
|
+
proxy = provider.get_proxy()
|
|
295
|
+
|
|
296
|
+
# As curl arguments: ["-x", "http://user:pass@host:port"]
|
|
297
|
+
subprocess.run(["curl", *proxy.format(ProxyFormat.CURL), "https://httpbin.org/ip"])
|
|
298
|
+
|
|
299
|
+
# Or as HTTP_PROXY / HTTPS_PROXY for any program that reads them
|
|
300
|
+
subprocess.run(
|
|
301
|
+
["curl", "https://httpbin.org/ip"],
|
|
302
|
+
env={**os.environ, **proxy.format(ProxyFormat.ENV)},
|
|
303
|
+
)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Use Your Own Proxy List
|
|
307
|
+
|
|
308
|
+
If your vendor has no provider here, or you simply have a list of proxies, `ProxyList` gives them the same interface as everything else:
|
|
309
|
+
|
|
310
|
+
```py
|
|
311
|
+
from proxyproviders import ProxyList
|
|
312
|
+
|
|
313
|
+
provider = ProxyList(
|
|
314
|
+
[
|
|
315
|
+
"http://user:pass@203.0.113.7:8080",
|
|
316
|
+
"203.0.113.8:8080:user:pass",
|
|
317
|
+
"socks5://user:pass@203.0.113.9:1080",
|
|
318
|
+
]
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# Or one proxy per line from a file (blank lines and # comments are skipped)
|
|
322
|
+
provider = ProxyList.from_file("proxies.txt")
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
A single proxy can be parsed with `Proxy.from_string(...)`, which accepts a URL, `user:pass@host:port`, `host:port` or `host:port:user:pass`.
|
|
326
|
+
|
|
327
|
+
## Automatic Rotation and Retries
|
|
328
|
+
|
|
329
|
+
`RotatingSession` is a `requests.Session` that picks a proxy for every request. If a request cannot get through its proxy, that proxy is set aside for a while and the request is sent again through the next one:
|
|
330
|
+
|
|
331
|
+
```py
|
|
332
|
+
from proxyproviders import RotatingSession
|
|
333
|
+
|
|
334
|
+
session = RotatingSession(provider, max_retries=3)
|
|
335
|
+
|
|
336
|
+
response = session.get("https://httpbin.org/ip", timeout=10)
|
|
337
|
+
print(response.json(), session.last_proxy)
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
It is opt-in: nothing changes for code that calls `get_proxy()` itself. What it treats as a failed proxy, and when it sends the request again:
|
|
341
|
+
|
|
342
|
+
- **Could not connect to the proxy, or open a tunnel through it:** retried for any HTTP method, because the request never reached the target.
|
|
343
|
+
- **Any other connection error or timeout**, such as a proxy that drops the connection or stops responding: retried for `GET`, `HEAD` and `OPTIONS` only. A `POST` may already have been acted on, so the error is raised instead of sending it twice.
|
|
344
|
+
- **A `407 Proxy Authentication Required` response**, or any status you list in `retry_statuses`: retried for any method.
|
|
345
|
+
|
|
346
|
+
Ordinary error statuses such as 404 or 500 are returned as usual. File uploads and streamed bodies are sent once and never retried. Deciding that a response means your proxy is blocked is up to you, for example:
|
|
347
|
+
|
|
348
|
+
```py
|
|
349
|
+
session = RotatingSession(provider, max_retries=3, retry_statuses={403, 429})
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
For httpx 0.26 or newer, sync or async, the same behaviour comes as a transport. Install it with `pip install "proxyproviders[httpx]"`:
|
|
353
|
+
|
|
354
|
+
```py
|
|
355
|
+
import httpx
|
|
356
|
+
from proxyproviders.httpx_transport import AsyncRotatingTransport, RotatingTransport
|
|
357
|
+
|
|
358
|
+
with httpx.Client(transport=RotatingTransport(provider, max_retries=3)) as client:
|
|
359
|
+
response = client.get("https://httpbin.org/ip")
|
|
360
|
+
|
|
361
|
+
async with httpx.AsyncClient(transport=AsyncRotatingTransport(provider)) as client:
|
|
362
|
+
response = await client.get("https://httpbin.org/ip")
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## Reporting Failed Proxies
|
|
366
|
+
|
|
367
|
+
When you make requests yourself, tell the provider about a proxy that failed and `get_proxy()` will avoid it for `failure_cooldown` seconds (60 by default):
|
|
368
|
+
|
|
369
|
+
```py
|
|
370
|
+
import requests
|
|
371
|
+
from proxyproviders import ProxyFormat
|
|
372
|
+
|
|
373
|
+
proxy = provider.get_proxy()
|
|
374
|
+
try:
|
|
375
|
+
response = requests.get(url, proxies=proxy.format(ProxyFormat.REQUESTS), timeout=10)
|
|
376
|
+
except requests.exceptions.ProxyError:
|
|
377
|
+
provider.report_failure(proxy)
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
`provider.report_success(proxy)` puts it back early, and `provider.available_proxies()` lists the ones not currently set aside. If every proxy has failed, `get_proxy()` still returns one instead of raising. This works the same with every HTTP client, sync or async.
|
|
381
|
+
|
|
382
|
+
## Choosing Which Proxy You Get
|
|
383
|
+
|
|
384
|
+
`get_proxy()` takes a selection algorithm. The default is round robin.
|
|
385
|
+
|
|
386
|
+
```py
|
|
387
|
+
from proxyproviders.algorithms import First, Random, RoundRobin
|
|
388
|
+
|
|
389
|
+
proxy = provider.get_proxy() # RoundRobin: next proxy in order
|
|
390
|
+
proxy = provider.get_proxy(Random()) # a random proxy
|
|
391
|
+
proxy = provider.get_proxy(First()) # always the first proxy
|
|
392
|
+
|
|
393
|
+
# A RoundRobin you create keeps its own position, separate from the provider's default
|
|
394
|
+
round_robin = RoundRobin()
|
|
395
|
+
proxy1 = provider.get_proxy(round_robin)
|
|
396
|
+
proxy2 = provider.get_proxy(round_robin) # next in sequence
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Write your own by subclassing `Algorithm`:
|
|
400
|
+
|
|
401
|
+
```py
|
|
402
|
+
from typing import List
|
|
403
|
+
from proxyproviders import Proxy
|
|
404
|
+
from proxyproviders.algorithms import Algorithm
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class USProxyAlgorithm(Algorithm):
|
|
408
|
+
"""Prefers US proxies, falls back to the first available."""
|
|
409
|
+
|
|
410
|
+
def select(self, proxies: List[Proxy]) -> Proxy:
|
|
411
|
+
for proxy in proxies:
|
|
412
|
+
if proxy.country_code == "US":
|
|
413
|
+
return proxy
|
|
414
|
+
return proxies[0]
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
proxy = provider.get_proxy(USProxyAlgorithm())
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## Configuration
|
|
421
|
+
|
|
422
|
+
Options shared by every provider live in `ProxyConfig` ([full reference](https://davidteather.github.io/proxyproviders/#proxyproviders.proxy_provider.ProxyConfig)):
|
|
423
|
+
|
|
424
|
+
| Option | Default | Meaning |
|
|
425
|
+
|---|---|---|
|
|
426
|
+
| `refresh_interval` | 600 | Seconds before the proxy list is fetched again. 0 fetches once and never refreshes. |
|
|
427
|
+
| `timeout` | 30 | Seconds to wait on each call to the provider's API |
|
|
428
|
+
| `refresh_retry_delay` | 30 | If a refresh fails, the previous list keeps being served and the API is tried again after this many seconds |
|
|
429
|
+
| `failure_cooldown` | 60 | Seconds `get_proxy()` avoids a proxy after `report_failure()` |
|
|
430
|
+
|
|
431
|
+
```py
|
|
432
|
+
import os
|
|
433
|
+
import time
|
|
434
|
+
from proxyproviders import ProxyConfig, Webshare
|
|
435
|
+
|
|
436
|
+
config = ProxyConfig(refresh_interval=3)
|
|
437
|
+
ws = Webshare(api_key=os.environ["WEBSHARE_API_KEY"], config=config)
|
|
438
|
+
|
|
439
|
+
ws.list_proxies() # calls the API
|
|
440
|
+
ws.list_proxies() # cached
|
|
441
|
+
|
|
442
|
+
time.sleep(5)
|
|
443
|
+
ws.list_proxies() # calls the API again, since more than 3s have passed
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
`list_proxies()` and `get_proxy()` are safe to call from several threads.
|
|
447
|
+
|
|
448
|
+
## SOCKS Proxies
|
|
449
|
+
|
|
450
|
+
A proxy whose `protocols` only lists a SOCKS scheme, e.g. `["socks5"]`, is formatted with that scheme for every client:
|
|
451
|
+
|
|
452
|
+
```py
|
|
453
|
+
proxy.format(ProxyFormat.REQUESTS)
|
|
454
|
+
# {'http': 'socks5://user:pass@host:1080', 'https': 'socks5://user:pass@host:1080'}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Your HTTP client needs SOCKS support for this to work. For requests, install it with:
|
|
458
|
+
|
|
459
|
+
```sh
|
|
460
|
+
python -m pip install "proxyproviders[socks]"
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
httpx needs `httpx[socks]`. aiohttp and Scrapy have no built-in SOCKS support.
|
|
464
|
+
|
|
465
|
+
## Making Your Own Proxy Provider
|
|
466
|
+
|
|
467
|
+
Subclass `ProxyProvider` and return a list of `Proxy` objects from `_fetch_proxies`. Caching, refreshing, selection, formatting and failure reporting then work without any more code:
|
|
468
|
+
|
|
469
|
+
```py
|
|
470
|
+
from typing import List, Optional
|
|
471
|
+
from proxyproviders import Proxy, ProxyConfig, ProxyProvider
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class MyProxyProvider(ProxyProvider):
|
|
475
|
+
def __init__(self, config: Optional[ProxyConfig] = None):
|
|
476
|
+
super().__init__(config=config)
|
|
477
|
+
|
|
478
|
+
def _fetch_proxies(self) -> List[Proxy]:
|
|
479
|
+
# Call your vendor's API here and convert each entry
|
|
480
|
+
return [
|
|
481
|
+
Proxy(
|
|
482
|
+
id="1",
|
|
483
|
+
username="username",
|
|
484
|
+
password="password",
|
|
485
|
+
proxy_address="192.168.1.1",
|
|
486
|
+
port=80,
|
|
487
|
+
)
|
|
488
|
+
]
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
provider = MyProxyProvider()
|
|
492
|
+
proxy = provider.get_proxy()
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
If you write one for a vendor other people use, please contribute it back. [CONTRIBUTING.md](https://github.com/davidteather/proxyproviders/blob/main/CONTRIBUTING.md) has the steps, and the repository includes an `add-provider` skill that walks a coding agent such as Claude Code through the whole process.
|
|
496
|
+
|
|
497
|
+
## How to Support The Project
|
|
498
|
+
|
|
499
|
+
- Star the repo 😎
|
|
500
|
+
- Consider [sponsoring](https://go.dteather.com/github-sponsors?src=proxyproviders&placement=readme) me on GitHub
|
|
501
|
+
- Send me an email or a [LinkedIn](https://www.linkedin.com/in/davidteather/) message about what you're doing with it :D
|
|
502
|
+
- Submit PRs for issues or any new providers/features :)
|
|
503
|
+
|
|
504
|
+
**Note:** If you want to learn how to web scrape websites check my [free and open-source course for learning everything web scraping](https://go.dteather.com/everything-web-scraping?src=proxyproviders&placement=readme)
|