-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcloudscraper_proxy.py
More file actions
219 lines (177 loc) · 7.87 KB
/
Copy pathcloudscraper_proxy.py
File metadata and controls
219 lines (177 loc) · 7.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
"""
CloudScraper extension for sending and receiving proxy headers.
This module provides a CloudScraper subclass that enables:
1. Sending custom headers to proxy servers during CONNECT
2. Capturing response headers from proxy servers
Example usage:
from python_proxy_headers.cloudscraper_proxy import create_scraper
scraper = create_scraper(proxy_headers={'X-ProxyMesh-Country': 'US'})
scraper.proxies = {'https': 'http://proxy:8080'}
response = scraper.get('https://example.com')
# Access proxy response headers (stored on the response object)
print(response.proxy_headers)
"""
from typing import Dict, Optional, Any
try:
import cloudscraper
from cloudscraper import CipherSuiteAdapter
except ImportError:
raise ImportError(
"cloudscraper is required for this module. "
"Install it with: pip install cloudscraper"
)
from .header_utils import validate_headers
from .urllib3_proxy_manager import proxy_from_url
class CipherSuiteProxyHeaderAdapter(CipherSuiteAdapter):
"""
Combines CloudScraper's CipherSuiteAdapter with proxy header support.
This adapter:
- Maintains CloudScraper's TLS/cipher suite customization
- Adds the ability to send custom headers to proxy servers
- Uses our custom ProxyManager that captures proxy response headers
"""
def __init__(self, proxy_headers: Optional[Dict[str, str]] = None, **kwargs):
self._proxy_headers = validate_headers(proxy_headers)
super().__init__(**kwargs)
def build_response(self, req, resp):
response = super().build_response(req, resp)
response.proxy_headers = getattr(resp, "proxy_headers", {}) or {}
return response
def proxy_manager_for(self, proxy, **proxy_kwargs):
"""
Return a ProxyManager for the given proxy with custom header support.
Overrides the default proxy_manager_for to use our custom ProxyManager
that supports sending and receiving proxy headers.
"""
if proxy in self.proxy_manager:
manager = self.proxy_manager[proxy]
elif proxy.lower().startswith("socks"):
# SOCKS proxies don't support custom headers
return super().proxy_manager_for(proxy, **proxy_kwargs)
else:
# Get standard proxy headers (e.g., Proxy-Authorization)
_proxy_headers = self.proxy_headers(proxy)
# Merge with our custom proxy headers
if self._proxy_headers:
_proxy_headers.update(self._proxy_headers)
# Pass SSL context if available
if hasattr(self, 'ssl_context') and self.ssl_context:
proxy_kwargs['ssl_context'] = self.ssl_context
if hasattr(self, 'source_address') and self.source_address:
proxy_kwargs['source_address'] = self.source_address
manager = self.proxy_manager[proxy] = proxy_from_url(
proxy,
proxy_headers=_proxy_headers,
num_pools=self._pool_connections,
maxsize=self._pool_maxsize,
block=self._pool_block,
**proxy_kwargs,
)
return manager
class ProxyCloudScraper(cloudscraper.CloudScraper):
"""
CloudScraper with proxy header support.
This class extends CloudScraper to add the ability to:
- Send custom headers to proxy servers during CONNECT tunneling
- Receive and access headers from proxy server responses
Args:
proxy_headers: Dict of headers to send to proxy servers
**kwargs: All other arguments passed to CloudScraper
Example:
scraper = ProxyCloudScraper(proxy_headers={'X-ProxyMesh-Country': 'US'})
scraper.proxies = {'https': 'http://proxy.example.com:8080'}
response = scraper.get('https://httpbin.org/ip')
print(response.proxy_headers) # Headers from proxy CONNECT response
"""
def __init__(self, proxy_headers: Optional[Dict[str, str]] = None, **kwargs):
self._proxy_headers = validate_headers(proxy_headers)
# Call parent init
super().__init__(**kwargs)
# Replace the HTTPS adapter with our proxy-header-aware version
# We need to preserve the cipher suite settings from the parent
self.mount(
'https://',
CipherSuiteProxyHeaderAdapter(
proxy_headers=self._proxy_headers,
cipherSuite=self.cipherSuite,
ecdhCurve=getattr(self, 'ecdhCurve', 'prime256v1'),
server_hostname=getattr(self, 'server_hostname', None),
source_address=getattr(self, 'source_address', None),
ssl_context=getattr(self, 'ssl_context', None)
)
)
# Also mount for HTTP (though proxy headers are mainly for HTTPS CONNECT)
self.mount(
'http://',
CipherSuiteProxyHeaderAdapter(
proxy_headers=self._proxy_headers,
cipherSuite=self.cipherSuite,
ecdhCurve=getattr(self, 'ecdhCurve', 'prime256v1'),
server_hostname=getattr(self, 'server_hostname', None),
source_address=getattr(self, 'source_address', None),
ssl_context=getattr(self, 'ssl_context', None)
)
)
def set_proxy_headers(self, proxy_headers: Dict[str, str]):
"""
Update the proxy headers and remount adapters.
Args:
proxy_headers: New proxy headers to use
"""
self._proxy_headers = validate_headers(proxy_headers)
# Remount adapters with new headers
self.mount(
'https://',
CipherSuiteProxyHeaderAdapter(
proxy_headers=self._proxy_headers,
cipherSuite=self.cipherSuite,
ecdhCurve=getattr(self, 'ecdhCurve', 'prime256v1'),
server_hostname=getattr(self, 'server_hostname', None),
source_address=getattr(self, 'source_address', None),
ssl_context=getattr(self, 'ssl_context', None)
)
)
self.mount(
'http://',
CipherSuiteProxyHeaderAdapter(
proxy_headers=self._proxy_headers,
cipherSuite=self.cipherSuite,
ecdhCurve=getattr(self, 'ecdhCurve', 'prime256v1'),
server_hostname=getattr(self, 'server_hostname', None),
source_address=getattr(self, 'source_address', None),
ssl_context=getattr(self, 'ssl_context', None)
)
)
def create_scraper(
proxy_headers: Optional[Dict[str, str]] = None,
sess: Optional[Any] = None,
**kwargs
) -> ProxyCloudScraper:
"""
Create a CloudScraper with proxy header support.
This is a drop-in replacement for cloudscraper.create_scraper() that
adds proxy header capabilities.
Args:
proxy_headers: Dict of headers to send to proxy servers
sess: Existing session to copy attributes from
**kwargs: All other arguments passed to CloudScraper
Returns:
ProxyCloudScraper instance
Example:
from python_proxy_headers.cloudscraper_proxy import create_scraper
scraper = create_scraper(
proxy_headers={'X-ProxyMesh-Country': 'US'},
browser='chrome'
)
scraper.proxies = {'https': 'http://proxy:8080'}
response = scraper.get('https://example.com')
"""
scraper = ProxyCloudScraper(proxy_headers=proxy_headers, **kwargs)
if sess:
for attr in ['auth', 'cert', 'cookies', 'headers', 'hooks', 'params', 'proxies', 'data']:
val = getattr(sess, attr, None)
if val is not None:
setattr(scraper, attr, val)
return scraper
# Convenience alias
session = create_scraper