scrapling 0.2.96__py3-none-any.whl → 0.2.98__py3-none-any.whl
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.
- scrapling/__init__.py +35 -6
- scrapling/core/custom_types.py +1 -3
- scrapling/core/storage_adaptors.py +3 -3
- scrapling/core/translator.py +4 -1
- scrapling/core/utils.py +1 -1
- scrapling/defaults.py +18 -9
- scrapling/engines/camo.py +123 -104
- scrapling/engines/pw.py +100 -75
- scrapling/engines/static.py +22 -42
- scrapling/engines/toolbelt/custom.py +2 -2
- scrapling/engines/toolbelt/fingerprints.py +2 -2
- scrapling/engines/toolbelt/navigation.py +1 -1
- scrapling/fetchers.py +24 -24
- scrapling/parser.py +6 -12
- {scrapling-0.2.96.dist-info → scrapling-0.2.98.dist-info}/METADATA +23 -22
- {scrapling-0.2.96.dist-info → scrapling-0.2.98.dist-info}/RECORD +20 -20
- {scrapling-0.2.96.dist-info → scrapling-0.2.98.dist-info}/WHEEL +1 -1
- {scrapling-0.2.96.dist-info → scrapling-0.2.98.dist-info}/LICENSE +0 -0
- {scrapling-0.2.96.dist-info → scrapling-0.2.98.dist-info}/entry_points.txt +0 -0
- {scrapling-0.2.96.dist-info → scrapling-0.2.98.dist-info}/top_level.txt +0 -0
scrapling/engines/pw.py
CHANGED
@@ -19,20 +19,20 @@ class PlaywrightEngine:
|
|
19
19
|
self, headless: Union[bool, str] = True,
|
20
20
|
disable_resources: bool = False,
|
21
21
|
useragent: Optional[str] = None,
|
22
|
-
network_idle:
|
22
|
+
network_idle: bool = False,
|
23
23
|
timeout: Optional[float] = 30000,
|
24
24
|
page_action: Callable = None,
|
25
25
|
wait_selector: Optional[str] = None,
|
26
26
|
locale: Optional[str] = 'en-US',
|
27
27
|
wait_selector_state: SelectorWaitStates = 'attached',
|
28
|
-
stealth:
|
29
|
-
real_chrome:
|
30
|
-
hide_canvas:
|
31
|
-
disable_webgl:
|
28
|
+
stealth: bool = False,
|
29
|
+
real_chrome: bool = False,
|
30
|
+
hide_canvas: bool = False,
|
31
|
+
disable_webgl: bool = False,
|
32
32
|
cdp_url: Optional[str] = None,
|
33
|
-
nstbrowser_mode:
|
33
|
+
nstbrowser_mode: bool = False,
|
34
34
|
nstbrowser_config: Optional[Dict] = None,
|
35
|
-
google_search:
|
35
|
+
google_search: bool = True,
|
36
36
|
extra_headers: Optional[Dict[str, str]] = None,
|
37
37
|
proxy: Optional[Union[str, Dict[str, str]]] = None,
|
38
38
|
adaptor_arguments: Dict = None
|
@@ -126,7 +126,7 @@ class PlaywrightEngine:
|
|
126
126
|
|
127
127
|
return cdp_url
|
128
128
|
|
129
|
-
@lru_cache(typed=True)
|
129
|
+
@lru_cache(32, typed=True)
|
130
130
|
def __set_flags(self):
|
131
131
|
"""Returns the flags that will be used while launching the browser if stealth mode is enabled"""
|
132
132
|
flags = DEFAULT_STEALTH_FLAGS
|
@@ -169,7 +169,7 @@ class PlaywrightEngine:
|
|
169
169
|
|
170
170
|
return context_kwargs
|
171
171
|
|
172
|
-
@lru_cache()
|
172
|
+
@lru_cache(1)
|
173
173
|
def __stealth_scripts(self):
|
174
174
|
# Basic bypasses nothing fancy as I'm still working on it
|
175
175
|
# But with adding these bypasses to the above config, it bypasses many online tests like
|
@@ -188,6 +188,38 @@ class PlaywrightEngine:
|
|
188
188
|
)
|
189
189
|
)
|
190
190
|
|
191
|
+
def _process_response_history(self, first_response):
|
192
|
+
"""Process response history to build a list of Response objects"""
|
193
|
+
history = []
|
194
|
+
current_request = first_response.request.redirected_from
|
195
|
+
|
196
|
+
try:
|
197
|
+
while current_request:
|
198
|
+
try:
|
199
|
+
current_response = current_request.response()
|
200
|
+
history.insert(0, Response(
|
201
|
+
url=current_request.url,
|
202
|
+
# using current_response.text() will trigger "Error: Response.text: Response body is unavailable for redirect responses"
|
203
|
+
text='',
|
204
|
+
body=b'',
|
205
|
+
status=current_response.status if current_response else 301,
|
206
|
+
reason=(current_response.status_text or StatusText.get(current_response.status)) if current_response else StatusText.get(301),
|
207
|
+
encoding=current_response.headers.get('content-type', '') or 'utf-8',
|
208
|
+
cookies={},
|
209
|
+
headers=current_response.all_headers() if current_response else {},
|
210
|
+
request_headers=current_request.all_headers(),
|
211
|
+
**self.adaptor_arguments
|
212
|
+
))
|
213
|
+
except Exception as e:
|
214
|
+
log.error(f"Error processing redirect: {e}")
|
215
|
+
break
|
216
|
+
|
217
|
+
current_request = current_request.redirected_from
|
218
|
+
except Exception as e:
|
219
|
+
log.error(f"Error processing response history: {e}")
|
220
|
+
|
221
|
+
return history
|
222
|
+
|
191
223
|
def fetch(self, url: str) -> Response:
|
192
224
|
"""Opens up the browser and do your request based on your chosen options.
|
193
225
|
|
@@ -201,8 +233,8 @@ class PlaywrightEngine:
|
|
201
233
|
else:
|
202
234
|
from rebrowser_playwright.sync_api import sync_playwright
|
203
235
|
|
204
|
-
# Store the final response
|
205
236
|
final_response = None
|
237
|
+
referer = generate_convincing_referer(url) if self.google_search else None
|
206
238
|
|
207
239
|
def handle_response(finished_response: PlaywrightResponse):
|
208
240
|
nonlocal final_response
|
@@ -218,11 +250,9 @@ class PlaywrightEngine:
|
|
218
250
|
browser = p.chromium.launch(**self.__launch_kwargs())
|
219
251
|
|
220
252
|
context = browser.new_context(**self.__context_kwargs())
|
221
|
-
# Finally we are in business
|
222
253
|
page = context.new_page()
|
223
254
|
page.set_default_navigation_timeout(self.timeout)
|
224
255
|
page.set_default_timeout(self.timeout)
|
225
|
-
# Listen for all responses
|
226
256
|
page.on("response", handle_response)
|
227
257
|
|
228
258
|
if self.extra_headers:
|
@@ -235,54 +265,51 @@ class PlaywrightEngine:
|
|
235
265
|
for script in self.__stealth_scripts():
|
236
266
|
page.add_init_script(path=script)
|
237
267
|
|
238
|
-
first_response = page.goto(url, referer=
|
268
|
+
first_response = page.goto(url, referer=referer)
|
239
269
|
page.wait_for_load_state(state="domcontentloaded")
|
270
|
+
|
240
271
|
if self.network_idle:
|
241
272
|
page.wait_for_load_state('networkidle')
|
242
273
|
|
243
274
|
if self.page_action is not None:
|
244
|
-
|
275
|
+
try:
|
276
|
+
page = self.page_action(page)
|
277
|
+
except Exception as e:
|
278
|
+
log.error(f"Error executing page_action: {e}")
|
245
279
|
|
246
280
|
if self.wait_selector and type(self.wait_selector) is str:
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
281
|
+
try:
|
282
|
+
waiter = page.locator(self.wait_selector)
|
283
|
+
waiter.first.wait_for(state=self.wait_selector_state)
|
284
|
+
# Wait again after waiting for the selector, helpful with protections like Cloudflare
|
285
|
+
page.wait_for_load_state(state="load")
|
286
|
+
page.wait_for_load_state(state="domcontentloaded")
|
287
|
+
if self.network_idle:
|
288
|
+
page.wait_for_load_state('networkidle')
|
289
|
+
except Exception as e:
|
290
|
+
log.error(f"Error waiting for selector {self.wait_selector}: {e}")
|
254
291
|
|
255
292
|
# In case we didn't catch a document type somehow
|
256
293
|
final_response = final_response if final_response else first_response
|
294
|
+
if not final_response:
|
295
|
+
raise ValueError("Failed to get a response from the page")
|
296
|
+
|
257
297
|
# This will be parsed inside `Response`
|
258
298
|
encoding = final_response.headers.get('content-type', '') or 'utf-8' # default encoding
|
259
299
|
# PlayWright API sometimes give empty status text for some reason!
|
260
300
|
status_text = final_response.status_text or StatusText.get(final_response.status)
|
261
301
|
|
262
|
-
history =
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
url=current_request.url,
|
269
|
-
# using current_response.text() will trigger "Error: Response.text: Response body is unavailable for redirect responses"
|
270
|
-
text='',
|
271
|
-
body=b'',
|
272
|
-
status=current_response.status if current_response else 301,
|
273
|
-
reason=(current_response.status_text or StatusText.get(current_response.status)) if current_response else StatusText.get(301),
|
274
|
-
encoding=current_response.headers.get('content-type', '') or 'utf-8',
|
275
|
-
cookies={},
|
276
|
-
headers=current_response.all_headers() if current_response else {},
|
277
|
-
request_headers=current_request.all_headers(),
|
278
|
-
**self.adaptor_arguments
|
279
|
-
))
|
280
|
-
current_request = current_request.redirected_from
|
302
|
+
history = self._process_response_history(first_response)
|
303
|
+
try:
|
304
|
+
page_content = page.content()
|
305
|
+
except Exception as e:
|
306
|
+
log.error(f"Error getting page content: {e}")
|
307
|
+
page_content = ""
|
281
308
|
|
282
309
|
response = Response(
|
283
310
|
url=page.url,
|
284
|
-
text=
|
285
|
-
body=
|
311
|
+
text=page_content,
|
312
|
+
body=page_content.encode('utf-8'),
|
286
313
|
status=final_response.status,
|
287
314
|
reason=status_text,
|
288
315
|
encoding=encoding,
|
@@ -293,6 +320,7 @@ class PlaywrightEngine:
|
|
293
320
|
**self.adaptor_arguments
|
294
321
|
)
|
295
322
|
page.close()
|
323
|
+
context.close()
|
296
324
|
return response
|
297
325
|
|
298
326
|
async def async_fetch(self, url: str) -> Response:
|
@@ -308,8 +336,8 @@ class PlaywrightEngine:
|
|
308
336
|
else:
|
309
337
|
from rebrowser_playwright.async_api import async_playwright
|
310
338
|
|
311
|
-
# Store the final response
|
312
339
|
final_response = None
|
340
|
+
referer = generate_convincing_referer(url) if self.google_search else None
|
313
341
|
|
314
342
|
async def handle_response(finished_response: PlaywrightResponse):
|
315
343
|
nonlocal final_response
|
@@ -325,11 +353,9 @@ class PlaywrightEngine:
|
|
325
353
|
browser = await p.chromium.launch(**self.__launch_kwargs())
|
326
354
|
|
327
355
|
context = await browser.new_context(**self.__context_kwargs())
|
328
|
-
# Finally we are in business
|
329
356
|
page = await context.new_page()
|
330
357
|
page.set_default_navigation_timeout(self.timeout)
|
331
358
|
page.set_default_timeout(self.timeout)
|
332
|
-
# Listen for all responses
|
333
359
|
page.on("response", handle_response)
|
334
360
|
|
335
361
|
if self.extra_headers:
|
@@ -342,54 +368,51 @@ class PlaywrightEngine:
|
|
342
368
|
for script in self.__stealth_scripts():
|
343
369
|
await page.add_init_script(path=script)
|
344
370
|
|
345
|
-
first_response = await page.goto(url, referer=
|
371
|
+
first_response = await page.goto(url, referer=referer)
|
346
372
|
await page.wait_for_load_state(state="domcontentloaded")
|
373
|
+
|
347
374
|
if self.network_idle:
|
348
375
|
await page.wait_for_load_state('networkidle')
|
349
376
|
|
350
377
|
if self.page_action is not None:
|
351
|
-
|
378
|
+
try:
|
379
|
+
page = await self.page_action(page)
|
380
|
+
except Exception as e:
|
381
|
+
log.error(f"Error executing async page_action: {e}")
|
352
382
|
|
353
383
|
if self.wait_selector and type(self.wait_selector) is str:
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
384
|
+
try:
|
385
|
+
waiter = page.locator(self.wait_selector)
|
386
|
+
await waiter.first.wait_for(state=self.wait_selector_state)
|
387
|
+
# Wait again after waiting for the selector, helpful with protections like Cloudflare
|
388
|
+
await page.wait_for_load_state(state="load")
|
389
|
+
await page.wait_for_load_state(state="domcontentloaded")
|
390
|
+
if self.network_idle:
|
391
|
+
await page.wait_for_load_state('networkidle')
|
392
|
+
except Exception as e:
|
393
|
+
log.error(f"Error waiting for selector {self.wait_selector}: {e}")
|
361
394
|
|
362
395
|
# In case we didn't catch a document type somehow
|
363
396
|
final_response = final_response if final_response else first_response
|
397
|
+
if not final_response:
|
398
|
+
raise ValueError("Failed to get a response from the page")
|
399
|
+
|
364
400
|
# This will be parsed inside `Response`
|
365
401
|
encoding = final_response.headers.get('content-type', '') or 'utf-8' # default encoding
|
366
402
|
# PlayWright API sometimes give empty status text for some reason!
|
367
403
|
status_text = final_response.status_text or StatusText.get(final_response.status)
|
368
404
|
|
369
|
-
history =
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
url=current_request.url,
|
376
|
-
# using current_response.text() will trigger "Error: Response.text: Response body is unavailable for redirect responses"
|
377
|
-
text='',
|
378
|
-
body=b'',
|
379
|
-
status=current_response.status if current_response else 301,
|
380
|
-
reason=(current_response.status_text or StatusText.get(current_response.status)) if current_response else StatusText.get(301),
|
381
|
-
encoding=current_response.headers.get('content-type', '') or 'utf-8',
|
382
|
-
cookies={},
|
383
|
-
headers=await current_response.all_headers() if current_response else {},
|
384
|
-
request_headers=await current_request.all_headers(),
|
385
|
-
**self.adaptor_arguments
|
386
|
-
))
|
387
|
-
current_request = current_request.redirected_from
|
405
|
+
history = self._process_response_history(first_response)
|
406
|
+
try:
|
407
|
+
page_content = await page.content()
|
408
|
+
except Exception as e:
|
409
|
+
log.error(f"Error getting page content in async: {e}")
|
410
|
+
page_content = ""
|
388
411
|
|
389
412
|
response = Response(
|
390
413
|
url=page.url,
|
391
|
-
text=
|
392
|
-
body=
|
414
|
+
text=page_content,
|
415
|
+
body=page_content.encode('utf-8'),
|
393
416
|
status=final_response.status,
|
394
417
|
reason=status_text,
|
395
418
|
encoding=encoding,
|
@@ -400,4 +423,6 @@ class PlaywrightEngine:
|
|
400
423
|
**self.adaptor_arguments
|
401
424
|
)
|
402
425
|
await page.close()
|
426
|
+
await context.close()
|
427
|
+
|
403
428
|
return response
|
scrapling/engines/static.py
CHANGED
@@ -7,10 +7,10 @@ from scrapling.core.utils import log, lru_cache
|
|
7
7
|
from .toolbelt import Response, generate_convincing_referer, generate_headers
|
8
8
|
|
9
9
|
|
10
|
-
@lru_cache(typed=True)
|
10
|
+
@lru_cache(2, typed=True) # Singleton easily
|
11
11
|
class StaticEngine:
|
12
12
|
def __init__(
|
13
|
-
self, url: str, proxy: Optional[str] = None, stealthy_headers:
|
13
|
+
self, url: str, proxy: Optional[str] = None, stealthy_headers: bool = True, follow_redirects: bool = True,
|
14
14
|
timeout: Optional[Union[int, float]] = None, retries: Optional[int] = 3, adaptor_arguments: Tuple = None
|
15
15
|
):
|
16
16
|
"""An engine that utilizes httpx library, check the `Fetcher` class for more documentation.
|
@@ -79,17 +79,25 @@ class StaticEngine:
|
|
79
79
|
**self.adaptor_arguments
|
80
80
|
)
|
81
81
|
|
82
|
+
def _make_request(self, method: str, **kwargs) -> Response:
|
83
|
+
headers = self._headers_job(kwargs.pop('headers', {}))
|
84
|
+
with httpx.Client(proxy=self.proxy, transport=httpx.HTTPTransport(retries=self.retries)) as client:
|
85
|
+
request = getattr(client, method)(url=self.url, headers=headers, follow_redirects=self.follow_redirects, timeout=self.timeout, **kwargs)
|
86
|
+
return self._prepare_response(request)
|
87
|
+
|
88
|
+
async def _async_make_request(self, method: str, **kwargs) -> Response:
|
89
|
+
headers = self._headers_job(kwargs.pop('headers', {}))
|
90
|
+
async with httpx.AsyncClient(proxy=self.proxy, transport=httpx.AsyncHTTPTransport(retries=self.retries)) as client:
|
91
|
+
request = await getattr(client, method)(url=self.url, headers=headers, follow_redirects=self.follow_redirects, timeout=self.timeout, **kwargs)
|
92
|
+
return self._prepare_response(request)
|
93
|
+
|
82
94
|
def get(self, **kwargs: Dict) -> Response:
|
83
95
|
"""Make basic HTTP GET request for you but with some added flavors.
|
84
96
|
|
85
97
|
:param kwargs: Any keyword arguments are passed directly to `httpx.get()` function so check httpx documentation for details.
|
86
98
|
:return: A `Response` object that is the same as `Adaptor` object except it has these added attributes: `status`, `reason`, `cookies`, `headers`, and `request_headers`
|
87
99
|
"""
|
88
|
-
|
89
|
-
with httpx.Client(proxy=self.proxy, transport=httpx.HTTPTransport(retries=self.retries)) as client:
|
90
|
-
request = client.get(url=self.url, headers=headers, follow_redirects=self.follow_redirects, timeout=self.timeout, **kwargs)
|
91
|
-
|
92
|
-
return self._prepare_response(request)
|
100
|
+
return self._make_request('get', **kwargs)
|
93
101
|
|
94
102
|
async def async_get(self, **kwargs: Dict) -> Response:
|
95
103
|
"""Make basic async HTTP GET request for you but with some added flavors.
|
@@ -97,11 +105,7 @@ class StaticEngine:
|
|
97
105
|
:param kwargs: Any keyword arguments are passed directly to `httpx.get()` function so check httpx documentation for details.
|
98
106
|
:return: A `Response` object that is the same as `Adaptor` object except it has these added attributes: `status`, `reason`, `cookies`, `headers`, and `request_headers`
|
99
107
|
"""
|
100
|
-
|
101
|
-
async with httpx.AsyncClient(proxy=self.proxy) as client:
|
102
|
-
request = await client.get(url=self.url, headers=headers, follow_redirects=self.follow_redirects, timeout=self.timeout, **kwargs)
|
103
|
-
|
104
|
-
return self._prepare_response(request)
|
108
|
+
return await self._async_make_request('get', **kwargs)
|
105
109
|
|
106
110
|
def post(self, **kwargs: Dict) -> Response:
|
107
111
|
"""Make basic HTTP POST request for you but with some added flavors.
|
@@ -109,11 +113,7 @@ class StaticEngine:
|
|
109
113
|
:param kwargs: Any keyword arguments are passed directly to `httpx.post()` function so check httpx documentation for details.
|
110
114
|
:return: A `Response` object that is the same as `Adaptor` object except it has these added attributes: `status`, `reason`, `cookies`, `headers`, and `request_headers`
|
111
115
|
"""
|
112
|
-
|
113
|
-
with httpx.Client(proxy=self.proxy, transport=httpx.HTTPTransport(retries=self.retries)) as client:
|
114
|
-
request = client.post(url=self.url, headers=headers, follow_redirects=self.follow_redirects, timeout=self.timeout, **kwargs)
|
115
|
-
|
116
|
-
return self._prepare_response(request)
|
116
|
+
return self._make_request('post', **kwargs)
|
117
117
|
|
118
118
|
async def async_post(self, **kwargs: Dict) -> Response:
|
119
119
|
"""Make basic async HTTP POST request for you but with some added flavors.
|
@@ -121,11 +121,7 @@ class StaticEngine:
|
|
121
121
|
:param kwargs: Any keyword arguments are passed directly to `httpx.post()` function so check httpx documentation for details.
|
122
122
|
:return: A `Response` object that is the same as `Adaptor` object except it has these added attributes: `status`, `reason`, `cookies`, `headers`, and `request_headers`
|
123
123
|
"""
|
124
|
-
|
125
|
-
async with httpx.AsyncClient(proxy=self.proxy) as client:
|
126
|
-
request = await client.post(url=self.url, headers=headers, follow_redirects=self.follow_redirects, timeout=self.timeout, **kwargs)
|
127
|
-
|
128
|
-
return self._prepare_response(request)
|
124
|
+
return await self._async_make_request('post', **kwargs)
|
129
125
|
|
130
126
|
def delete(self, **kwargs: Dict) -> Response:
|
131
127
|
"""Make basic HTTP DELETE request for you but with some added flavors.
|
@@ -133,11 +129,7 @@ class StaticEngine:
|
|
133
129
|
:param kwargs: Any keyword arguments are passed directly to `httpx.delete()` function so check httpx documentation for details.
|
134
130
|
:return: A `Response` object that is the same as `Adaptor` object except it has these added attributes: `status`, `reason`, `cookies`, `headers`, and `request_headers`
|
135
131
|
"""
|
136
|
-
|
137
|
-
with httpx.Client(proxy=self.proxy, transport=httpx.HTTPTransport(retries=self.retries)) as client:
|
138
|
-
request = client.delete(url=self.url, headers=headers, follow_redirects=self.follow_redirects, timeout=self.timeout, **kwargs)
|
139
|
-
|
140
|
-
return self._prepare_response(request)
|
132
|
+
return self._make_request('delete', **kwargs)
|
141
133
|
|
142
134
|
async def async_delete(self, **kwargs: Dict) -> Response:
|
143
135
|
"""Make basic async HTTP DELETE request for you but with some added flavors.
|
@@ -145,11 +137,7 @@ class StaticEngine:
|
|
145
137
|
:param kwargs: Any keyword arguments are passed directly to `httpx.delete()` function so check httpx documentation for details.
|
146
138
|
:return: A `Response` object that is the same as `Adaptor` object except it has these added attributes: `status`, `reason`, `cookies`, `headers`, and `request_headers`
|
147
139
|
"""
|
148
|
-
|
149
|
-
async with httpx.AsyncClient(proxy=self.proxy) as client:
|
150
|
-
request = await client.delete(url=self.url, headers=headers, follow_redirects=self.follow_redirects, timeout=self.timeout, **kwargs)
|
151
|
-
|
152
|
-
return self._prepare_response(request)
|
140
|
+
return await self._async_make_request('delete', **kwargs)
|
153
141
|
|
154
142
|
def put(self, **kwargs: Dict) -> Response:
|
155
143
|
"""Make basic HTTP PUT request for you but with some added flavors.
|
@@ -157,11 +145,7 @@ class StaticEngine:
|
|
157
145
|
:param kwargs: Any keyword arguments are passed directly to `httpx.put()` function so check httpx documentation for details.
|
158
146
|
:return: A `Response` object that is the same as `Adaptor` object except it has these added attributes: `status`, `reason`, `cookies`, `headers`, and `request_headers`
|
159
147
|
"""
|
160
|
-
|
161
|
-
with httpx.Client(proxy=self.proxy, transport=httpx.HTTPTransport(retries=self.retries)) as client:
|
162
|
-
request = client.put(url=self.url, headers=headers, follow_redirects=self.follow_redirects, timeout=self.timeout, **kwargs)
|
163
|
-
|
164
|
-
return self._prepare_response(request)
|
148
|
+
return self._make_request('put', **kwargs)
|
165
149
|
|
166
150
|
async def async_put(self, **kwargs: Dict) -> Response:
|
167
151
|
"""Make basic async HTTP PUT request for you but with some added flavors.
|
@@ -169,8 +153,4 @@ class StaticEngine:
|
|
169
153
|
:param kwargs: Any keyword arguments are passed directly to `httpx.put()` function so check httpx documentation for details.
|
170
154
|
:return: A `Response` object that is the same as `Adaptor` object except it has these added attributes: `status`, `reason`, `cookies`, `headers`, and `request_headers`
|
171
155
|
"""
|
172
|
-
|
173
|
-
async with httpx.AsyncClient(proxy=self.proxy) as client:
|
174
|
-
request = await client.put(url=self.url, headers=headers, follow_redirects=self.follow_redirects, timeout=self.timeout, **kwargs)
|
175
|
-
|
176
|
-
return self._prepare_response(request)
|
156
|
+
return await self._async_make_request('put', **kwargs)
|
@@ -16,7 +16,7 @@ class ResponseEncoding:
|
|
16
16
|
__ISO_8859_1_CONTENT_TYPES = {"text/plain", "text/html", "text/css", "text/javascript"}
|
17
17
|
|
18
18
|
@classmethod
|
19
|
-
@lru_cache(maxsize=
|
19
|
+
@lru_cache(maxsize=128)
|
20
20
|
def __parse_content_type(cls, header_value: str) -> Tuple[str, Dict[str, str]]:
|
21
21
|
"""Parse content type and parameters from a content-type header value.
|
22
22
|
|
@@ -38,7 +38,7 @@ class ResponseEncoding:
|
|
38
38
|
return content_type, params
|
39
39
|
|
40
40
|
@classmethod
|
41
|
-
@lru_cache(maxsize=
|
41
|
+
@lru_cache(maxsize=128)
|
42
42
|
def get_value(cls, content_type: Optional[str], text: Optional[str] = 'test') -> str:
|
43
43
|
"""Determine the appropriate character encoding from a content-type header.
|
44
44
|
|
@@ -12,7 +12,7 @@ from scrapling.core._types import Dict, Union
|
|
12
12
|
from scrapling.core.utils import lru_cache
|
13
13
|
|
14
14
|
|
15
|
-
@lru_cache(
|
15
|
+
@lru_cache(10, typed=True)
|
16
16
|
def generate_convincing_referer(url: str) -> str:
|
17
17
|
"""Takes the domain from the URL without the subdomain/suffix and make it look like you were searching google for this website
|
18
18
|
|
@@ -26,7 +26,7 @@ def generate_convincing_referer(url: str) -> str:
|
|
26
26
|
return f'https://www.google.com/search?q={website_name}'
|
27
27
|
|
28
28
|
|
29
|
-
@lru_cache(
|
29
|
+
@lru_cache(1, typed=True)
|
30
30
|
def get_os_name() -> Union[str, None]:
|
31
31
|
"""Get the current OS name in the same format needed for browserforge
|
32
32
|
|
@@ -110,7 +110,7 @@ def construct_cdp_url(cdp_url: str, query_params: Optional[Dict] = None) -> str:
|
|
110
110
|
raise ValueError(f"Invalid CDP URL: {str(e)}")
|
111
111
|
|
112
112
|
|
113
|
-
@lru_cache(
|
113
|
+
@lru_cache(10, typed=True)
|
114
114
|
def js_bypass_path(filename: str) -> str:
|
115
115
|
"""Takes the base filename of JS file inside the `bypasses` folder then return the full path of it
|
116
116
|
|
scrapling/fetchers.py
CHANGED
@@ -11,7 +11,7 @@ class Fetcher(BaseFetcher):
|
|
11
11
|
Any additional keyword arguments passed to the methods below are passed to the respective httpx's method directly.
|
12
12
|
"""
|
13
13
|
def get(
|
14
|
-
self, url: str, follow_redirects: bool = True, timeout: Optional[Union[int, float]] = 10, stealthy_headers:
|
14
|
+
self, url: str, follow_redirects: bool = True, timeout: Optional[Union[int, float]] = 10, stealthy_headers: bool = True,
|
15
15
|
proxy: Optional[str] = None, retries: Optional[int] = 3, **kwargs: Dict) -> Response:
|
16
16
|
"""Make basic HTTP GET request for you but with some added flavors.
|
17
17
|
|
@@ -30,7 +30,7 @@ class Fetcher(BaseFetcher):
|
|
30
30
|
return response_object
|
31
31
|
|
32
32
|
def post(
|
33
|
-
self, url: str, follow_redirects: bool = True, timeout: Optional[Union[int, float]] = 10, stealthy_headers:
|
33
|
+
self, url: str, follow_redirects: bool = True, timeout: Optional[Union[int, float]] = 10, stealthy_headers: bool = True,
|
34
34
|
proxy: Optional[str] = None, retries: Optional[int] = 3, **kwargs: Dict) -> Response:
|
35
35
|
"""Make basic HTTP POST request for you but with some added flavors.
|
36
36
|
|
@@ -49,7 +49,7 @@ class Fetcher(BaseFetcher):
|
|
49
49
|
return response_object
|
50
50
|
|
51
51
|
def put(
|
52
|
-
self, url: str, follow_redirects: bool = True, timeout: Optional[Union[int, float]] = 10, stealthy_headers:
|
52
|
+
self, url: str, follow_redirects: bool = True, timeout: Optional[Union[int, float]] = 10, stealthy_headers: bool = True,
|
53
53
|
proxy: Optional[str] = None, retries: Optional[int] = 3, **kwargs: Dict) -> Response:
|
54
54
|
"""Make basic HTTP PUT request for you but with some added flavors.
|
55
55
|
|
@@ -69,7 +69,7 @@ class Fetcher(BaseFetcher):
|
|
69
69
|
return response_object
|
70
70
|
|
71
71
|
def delete(
|
72
|
-
self, url: str, follow_redirects: bool = True, timeout: Optional[Union[int, float]] = 10, stealthy_headers:
|
72
|
+
self, url: str, follow_redirects: bool = True, timeout: Optional[Union[int, float]] = 10, stealthy_headers: bool = True,
|
73
73
|
proxy: Optional[str] = None, retries: Optional[int] = 3, **kwargs: Dict) -> Response:
|
74
74
|
"""Make basic HTTP DELETE request for you but with some added flavors.
|
75
75
|
|
@@ -90,7 +90,7 @@ class Fetcher(BaseFetcher):
|
|
90
90
|
|
91
91
|
class AsyncFetcher(Fetcher):
|
92
92
|
async def get(
|
93
|
-
self, url: str, follow_redirects: bool = True, timeout: Optional[Union[int, float]] = 10, stealthy_headers:
|
93
|
+
self, url: str, follow_redirects: bool = True, timeout: Optional[Union[int, float]] = 10, stealthy_headers: bool = True,
|
94
94
|
proxy: Optional[str] = None, retries: Optional[int] = 3, **kwargs: Dict) -> Response:
|
95
95
|
"""Make basic HTTP GET request for you but with some added flavors.
|
96
96
|
|
@@ -109,7 +109,7 @@ class AsyncFetcher(Fetcher):
|
|
109
109
|
return response_object
|
110
110
|
|
111
111
|
async def post(
|
112
|
-
self, url: str, follow_redirects: bool = True, timeout: Optional[Union[int, float]] = 10, stealthy_headers:
|
112
|
+
self, url: str, follow_redirects: bool = True, timeout: Optional[Union[int, float]] = 10, stealthy_headers: bool = True,
|
113
113
|
proxy: Optional[str] = None, retries: Optional[int] = 3, **kwargs: Dict) -> Response:
|
114
114
|
"""Make basic HTTP POST request for you but with some added flavors.
|
115
115
|
|
@@ -128,7 +128,7 @@ class AsyncFetcher(Fetcher):
|
|
128
128
|
return response_object
|
129
129
|
|
130
130
|
async def put(
|
131
|
-
self, url: str, follow_redirects: bool = True, timeout: Optional[Union[int, float]] = 10, stealthy_headers:
|
131
|
+
self, url: str, follow_redirects: bool = True, timeout: Optional[Union[int, float]] = 10, stealthy_headers: bool = True,
|
132
132
|
proxy: Optional[str] = None, retries: Optional[int] = 3, **kwargs: Dict) -> Response:
|
133
133
|
"""Make basic HTTP PUT request for you but with some added flavors.
|
134
134
|
|
@@ -147,7 +147,7 @@ class AsyncFetcher(Fetcher):
|
|
147
147
|
return response_object
|
148
148
|
|
149
149
|
async def delete(
|
150
|
-
self, url: str, follow_redirects: bool = True, timeout: Optional[Union[int, float]] = 10, stealthy_headers:
|
150
|
+
self, url: str, follow_redirects: bool = True, timeout: Optional[Union[int, float]] = 10, stealthy_headers: bool = True,
|
151
151
|
proxy: Optional[str] = None, retries: Optional[int] = 3, **kwargs: Dict) -> Response:
|
152
152
|
"""Make basic HTTP DELETE request for you but with some added flavors.
|
153
153
|
|
@@ -173,11 +173,11 @@ class StealthyFetcher(BaseFetcher):
|
|
173
173
|
Other added flavors include setting the faked OS fingerprints to match the user's OS and the referer of every request is set as if this request came from Google's search of this URL's domain.
|
174
174
|
"""
|
175
175
|
def fetch(
|
176
|
-
self, url: str, headless:
|
177
|
-
block_webrtc:
|
176
|
+
self, url: str, headless: Union[bool, Literal['virtual']] = True, block_images: bool = False, disable_resources: bool = False,
|
177
|
+
block_webrtc: bool = False, allow_webgl: bool = True, network_idle: bool = False, addons: Optional[List[str]] = None,
|
178
178
|
timeout: Optional[float] = 30000, page_action: Callable = None, wait_selector: Optional[str] = None, humanize: Optional[Union[bool, float]] = True,
|
179
|
-
wait_selector_state: SelectorWaitStates = 'attached', google_search:
|
180
|
-
proxy: Optional[Union[str, Dict[str, str]]] = None, os_randomize:
|
179
|
+
wait_selector_state: SelectorWaitStates = 'attached', google_search: bool = True, extra_headers: Optional[Dict[str, str]] = None,
|
180
|
+
proxy: Optional[Union[str, Dict[str, str]]] = None, os_randomize: bool = False, disable_ads: bool = False, geoip: bool = False,
|
181
181
|
) -> Response:
|
182
182
|
"""
|
183
183
|
Opens up a browser and do your request based on your chosen options below.
|
@@ -231,11 +231,11 @@ class StealthyFetcher(BaseFetcher):
|
|
231
231
|
return engine.fetch(url)
|
232
232
|
|
233
233
|
async def async_fetch(
|
234
|
-
self, url: str, headless:
|
235
|
-
block_webrtc:
|
234
|
+
self, url: str, headless: Union[bool, Literal['virtual']] = True, block_images: bool = False, disable_resources: bool = False,
|
235
|
+
block_webrtc: bool = False, allow_webgl: bool = True, network_idle: bool = False, addons: Optional[List[str]] = None,
|
236
236
|
timeout: Optional[float] = 30000, page_action: Callable = None, wait_selector: Optional[str] = None, humanize: Optional[Union[bool, float]] = True,
|
237
|
-
wait_selector_state: SelectorWaitStates = 'attached', google_search:
|
238
|
-
proxy: Optional[Union[str, Dict[str, str]]] = None, os_randomize:
|
237
|
+
wait_selector_state: SelectorWaitStates = 'attached', google_search: bool = True, extra_headers: Optional[Dict[str, str]] = None,
|
238
|
+
proxy: Optional[Union[str, Dict[str, str]]] = None, os_randomize: bool = False, disable_ads: bool = False, geoip: bool = False,
|
239
239
|
) -> Response:
|
240
240
|
"""
|
241
241
|
Opens up a browser and do your request based on your chosen options below.
|
@@ -307,13 +307,13 @@ class PlayWrightFetcher(BaseFetcher):
|
|
307
307
|
"""
|
308
308
|
def fetch(
|
309
309
|
self, url: str, headless: Union[bool, str] = True, disable_resources: bool = None,
|
310
|
-
useragent: Optional[str] = None, network_idle:
|
310
|
+
useragent: Optional[str] = None, network_idle: bool = False, timeout: Optional[float] = 30000,
|
311
311
|
page_action: Optional[Callable] = None, wait_selector: Optional[str] = None, wait_selector_state: SelectorWaitStates = 'attached',
|
312
|
-
hide_canvas:
|
312
|
+
hide_canvas: bool = False, disable_webgl: bool = False, extra_headers: Optional[Dict[str, str]] = None, google_search: bool = True,
|
313
313
|
proxy: Optional[Union[str, Dict[str, str]]] = None, locale: Optional[str] = 'en-US',
|
314
|
-
stealth:
|
314
|
+
stealth: bool = False, real_chrome: bool = False,
|
315
315
|
cdp_url: Optional[str] = None,
|
316
|
-
nstbrowser_mode:
|
316
|
+
nstbrowser_mode: bool = False, nstbrowser_config: Optional[Dict] = None,
|
317
317
|
) -> Response:
|
318
318
|
"""Opens up a browser and do your request based on your chosen options below.
|
319
319
|
|
@@ -367,13 +367,13 @@ class PlayWrightFetcher(BaseFetcher):
|
|
367
367
|
|
368
368
|
async def async_fetch(
|
369
369
|
self, url: str, headless: Union[bool, str] = True, disable_resources: bool = None,
|
370
|
-
useragent: Optional[str] = None, network_idle:
|
370
|
+
useragent: Optional[str] = None, network_idle: bool = False, timeout: Optional[float] = 30000,
|
371
371
|
page_action: Optional[Callable] = None, wait_selector: Optional[str] = None, wait_selector_state: SelectorWaitStates = 'attached',
|
372
|
-
hide_canvas:
|
372
|
+
hide_canvas: bool = False, disable_webgl: bool = False, extra_headers: Optional[Dict[str, str]] = None, google_search: bool = True,
|
373
373
|
proxy: Optional[Union[str, Dict[str, str]]] = None, locale: Optional[str] = 'en-US',
|
374
|
-
stealth:
|
374
|
+
stealth: bool = False, real_chrome: bool = False,
|
375
375
|
cdp_url: Optional[str] = None,
|
376
|
-
nstbrowser_mode:
|
376
|
+
nstbrowser_mode: bool = False, nstbrowser_config: Optional[Dict] = None,
|
377
377
|
) -> Response:
|
378
378
|
"""Opens up a browser and do your request based on your chosen options below.
|
379
379
|
|