11import requests
22from typing import List , Dict , Optional
33from ..proxy_provider import ProxyProvider , ProxyConfig
4- from ..exceptions import ProxyFetchException ,ProxyInvalidResponseException
4+ from ..exceptions import ProxyFetchException , ProxyInvalidResponseException
55from ..models .proxy import Proxy
66import threading
77
8+
89class BrightData (ProxyProvider ):
910 """BrightData (formerly luminati) is a proxy provider that offers residential and datacenter proxies.
1011
1112 Create an account `here <https://get.brightdata.com/davidteather>`_ (affiliate link).
12-
13+
1314 You can find your API key in the account settings `here <https://brightdata.com/cp/setting/users>`_ then create "add token" with scope "limit" (`BrightData article <https://docs.brightdata.com/general/account/api-token>`_ for more info)
1415
1516 The BrightData API documentation is `here <https://docs.brightdata.com/api-reference/account-management-api/Get_active_Zones?playground=open>`_
@@ -36,12 +37,20 @@ class BrightData(ProxyProvider):
3637 proxy_provider = BrightData(api_key="your-api-key", zone="my_zone", use_super_proxy=False)
3738 proxies = proxy_provider.list_proxies() # returns multiple proxies for each IP in the zone (potentially thousands)
3839 """
40+
3941 _BASE_URL = "https://api.brightdata.com"
4042 _SUPER_PROXY_ADDRESS = "brd.superproxy.io"
4143 _SUPER_PROXY_PORT = 33335
42- _PROTOCOLS : List [str ] = ["http" , "https" ] # BrightData supports both HTTP and HTTPS
43-
44- def __init__ (self , api_key : str , zone : str , username_suffix : Optional [str ] = None , use_super_proxy : Optional [bool ] = True , config : Optional [ProxyConfig ] = None ):
44+ _PROTOCOLS : List [str ] = ["http" , "https" ] # BrightData supports both HTTP and HTTPS
45+
46+ def __init__ (
47+ self ,
48+ api_key : str ,
49+ zone : str ,
50+ username_suffix : Optional [str ] = None ,
51+ use_super_proxy : Optional [bool ] = True ,
52+ config : Optional [ProxyConfig ] = None ,
53+ ):
4554 super ().__init__ (config )
4655 self .api_key = api_key
4756 self .zone = zone
@@ -57,15 +66,17 @@ def _fetch_proxies(self) -> List[Proxy]:
5766
5867 if self .use_super_proxy :
5968 # Let the super proxy handle the IP rotation
60- return [Proxy (
61- id = "super" ,
62- username = username ,
63- password = passwords ["passwords" ][0 ],
64- proxy_address = self ._SUPER_PROXY_ADDRESS ,
65- port = self ._SUPER_PROXY_PORT ,
66- protocols = self ._PROTOCOLS ,
67- )]
68-
69+ return [
70+ Proxy (
71+ id = "super" ,
72+ username = username ,
73+ password = passwords ["passwords" ][0 ],
74+ proxy_address = self ._SUPER_PROXY_ADDRESS ,
75+ port = self ._SUPER_PROXY_PORT ,
76+ protocols = self ._PROTOCOLS ,
77+ )
78+ ]
79+
6980 proxies = []
7081
7182 # Fetch all IPs in the zone, and create a proxy for each
@@ -75,22 +86,23 @@ def _fetch_proxies(self) -> List[Proxy]:
7586 for ip in ips :
7687 ip_targeted_username = username + f"-ip-{ ip ['ip' ]} "
7788
78- proxies .append (Proxy (
79- id = ip ["ip" ],
80- username = ip_targeted_username ,
81- password = passwords ["passwords" ][0 ],
82- proxy_address = self ._SUPER_PROXY_ADDRESS ,
83- port = self ._SUPER_PROXY_PORT ,
84- country_code = ip ["country" ],
85- protocols = self ._PROTOCOLS ,
86- ))
89+ proxies .append (
90+ Proxy (
91+ id = ip ["ip" ],
92+ username = ip_targeted_username ,
93+ password = passwords ["passwords" ][0 ],
94+ proxy_address = self ._SUPER_PROXY_ADDRESS ,
95+ port = self ._SUPER_PROXY_PORT ,
96+ country_code = ip ["country" ],
97+ protocols = self ._PROTOCOLS ,
98+ )
99+ )
87100
88101 return proxies
89102
90-
91103 def get_active_zones (self ) -> Dict :
92104 """Fetches active zones from BrightData API.
93-
105+
94106 Response:
95107
96108 .. code-block:: json
@@ -104,27 +116,26 @@ def get_active_zones(self) -> Dict:
104116
105117 """
106118 return self ._make_request ("/zone/get_active_zones" , "GET" )
107-
119+
108120 def get_zone_username (self , zone : str ) -> str :
109121 """Fetches zone username for the given zone ID from BrightData API.
110-
122+
111123 Note: this isn't directly an API endpoint, I'm sort of reconstructing some things here and it seems to behave a little weird.
112124
113125 :param zone: The zone ID to fetch username for
114126 """
115127
116128 data = self ._make_request (f"/status" , "GET" , params = {"zone" : zone })
117-
129+
118130 customer = data .get ("customer" )
119131 if not customer :
120132 raise ProxyInvalidResponseException ("Failed to fetch customer data" )
121-
122- return f"brd-customer-{ customer } -zone-{ zone } "
123133
134+ return f"brd-customer-{ customer } -zone-{ zone } "
124135
125136 def get_zone_passwords (self , zone : str ) -> Dict :
126137 """Fetches zone passwords from BrightData API.
127-
138+
128139 :param zone: The zone ID to fetch passwords for
129140
130141 Response:
@@ -141,10 +152,10 @@ def get_zone_passwords(self, zone: str) -> Dict:
141152
142153 """
143154 return self ._make_request (f"/zone/passwords" , "GET" , params = {"zone" : zone })
144-
155+
145156 def list_all_ips_in_zone (self , zone : str , country : Optional [str ] = None ) -> Dict :
146157 """Fetches all IPs in a zone from BrightData API.
147-
158+
148159 :param zone: The zone ID to fetch IPs for
149160 :param country: Optional 2-letter country code to filter IPs by
150161
@@ -159,13 +170,23 @@ def list_all_ips_in_zone(self, zone: str, country: Optional[str] = None) -> Dict
159170 "country": "US",
160171 }
161172 ]
162-
173+
163174 """
164- return self ._make_request (f"/zone/route_ips" , "GET" , params = {"zone" : zone , "list_countries" : True , "country" : country })
165-
166- def _make_request (self , path : str , method : str , params : Optional [Dict ] = None , json : Optional [Dict ] = None ) -> Dict :
175+ return self ._make_request (
176+ f"/zone/route_ips" ,
177+ "GET" ,
178+ params = {"zone" : zone , "list_countries" : True , "country" : country },
179+ )
180+
181+ def _make_request (
182+ self ,
183+ path : str ,
184+ method : str ,
185+ params : Optional [Dict ] = None ,
186+ json : Optional [Dict ] = None ,
187+ ) -> Dict :
167188 """Makes a request to the BrightData API.
168-
189+
169190 :param path: The path to the endpoint
170191 :param method: The HTTP method to use
171192 :param params: Optional parameters to include in the request
@@ -177,15 +198,20 @@ def _make_request(self, path: str, method: str, params: Optional[Dict] = None, j
177198 }
178199
179200 url = f"{ self ._BASE_URL } { path } "
180- response = requests .request (method , url , headers = headers , params = params , json = json )
201+ response = requests .request (
202+ method , url , headers = headers , params = params , json = json
203+ )
181204
182205 if response .status_code != 200 :
183- raise ProxyFetchException (f"Failed to fetch from BrightData, got status code { response .status_code } , text: { response .text } " )
206+ raise ProxyFetchException (
207+ f"Failed to fetch from BrightData, got status code { response .status_code } , text: { response .text } "
208+ )
184209
185- try :
210+ try :
186211 data = response .json ()
187212 except Exception as e :
188- raise ProxyInvalidResponseException (f"Failed to parse response: { str (e )} " ) from e
189-
213+ raise ProxyInvalidResponseException (
214+ f"Failed to parse response: { str (e )} "
215+ ) from e
190216
191- return data
217+ return data
0 commit comments