scrapely-python 0.1.1__py3-none-any.whl → 0.1.2__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.
- scrapely/__init__.py +1 -1
- scrapely/actor.py +5 -4
- scrapely/proxy_configuration.py +29 -6
- {scrapely_python-0.1.1.dist-info → scrapely_python-0.1.2.dist-info}/METADATA +3 -4
- {scrapely_python-0.1.1.dist-info → scrapely_python-0.1.2.dist-info}/RECORD +7 -7
- {scrapely_python-0.1.1.dist-info → scrapely_python-0.1.2.dist-info}/WHEEL +0 -0
- {scrapely_python-0.1.1.dist-info → scrapely_python-0.1.2.dist-info}/top_level.txt +0 -0
scrapely/__init__.py
CHANGED
scrapely/actor.py
CHANGED
|
@@ -1776,19 +1776,20 @@ class Actor(metaclass=_ActorMeta):
|
|
|
1776
1776
|
"""
|
|
1777
1777
|
cls._ensure_initialized('create_proxy_configuration')
|
|
1778
1778
|
|
|
1779
|
-
from .proxy_configuration import ProxyConfiguration
|
|
1779
|
+
from .proxy_configuration import ProxyConfiguration, _input_use_platform_proxy
|
|
1780
1780
|
|
|
1781
1781
|
# Handle actor_proxy_input (Node SDK normalizeProxyInput/createProxyConfiguration
|
|
1782
|
-
# parity): `
|
|
1782
|
+
# parity): `useScrapelyProxy: false` (or the legacy `useApifyProxy: false`; a
|
|
1783
|
+
# boolean useScrapelyProxy wins) explicitly disables the PLATFORM proxy only —
|
|
1783
1784
|
# custom proxies (proxyUrls / tiered lists / new_url_function) from the same
|
|
1784
1785
|
# input must still be used. The standard "Custom proxy URLs" input shape is
|
|
1785
|
-
# {
|
|
1786
|
+
# {useScrapelyProxy: false, proxyUrls: [...]}; returning None for it would
|
|
1786
1787
|
# silently drop the user's own proxies and crawl on their real IPs. We only
|
|
1787
1788
|
# bail when no custom proxies are given either. A MISSING key falls back to
|
|
1788
1789
|
# platform mode when the server injected credentials — input like
|
|
1789
1790
|
# {apifyProxyGroups: [...]} must not silently disable proxying.
|
|
1790
1791
|
if actor_proxy_input is not None:
|
|
1791
|
-
if actor_proxy_input
|
|
1792
|
+
if _input_use_platform_proxy(actor_proxy_input) is False and not (
|
|
1792
1793
|
proxy_urls
|
|
1793
1794
|
or new_url_function
|
|
1794
1795
|
or tiered_proxy_urls
|
scrapely/proxy_configuration.py
CHANGED
|
@@ -108,6 +108,19 @@ def _sanitize_fragment(value: str) -> str:
|
|
|
108
108
|
return re.sub(r'[:@/?#\s]', '', value)
|
|
109
109
|
|
|
110
110
|
|
|
111
|
+
def _input_use_platform_proxy(actor_proxy_input: Dict[str, Any]) -> Optional[bool]:
|
|
112
|
+
"""The proxy input field's platform-proxy switch, or None when it has none.
|
|
113
|
+
|
|
114
|
+
`useScrapelyProxy` wins when it is a boolean; otherwise the legacy
|
|
115
|
+
`useApifyProxy` key of existing inputs and ported actors decides.
|
|
116
|
+
"""
|
|
117
|
+
for key in ('useScrapelyProxy', 'useApifyProxy'):
|
|
118
|
+
value = actor_proxy_input.get(key)
|
|
119
|
+
if isinstance(value, bool):
|
|
120
|
+
return value
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
|
|
111
124
|
def _platform_env() -> Optional[Dict[str, Any]]:
|
|
112
125
|
"""Read platform proxy credentials from the environment (or None)."""
|
|
113
126
|
hostname = os.environ.get('SCRAPELY_PROXY_HOSTNAME') or os.environ.get('APIFY_PROXY_HOSTNAME')
|
|
@@ -213,7 +226,7 @@ class ProxyConfiguration:
|
|
|
213
226
|
|
|
214
227
|
# If actor_proxy_input is provided, extract settings from it
|
|
215
228
|
if actor_proxy_input:
|
|
216
|
-
if actor_proxy_input
|
|
229
|
+
if _input_use_platform_proxy(actor_proxy_input):
|
|
217
230
|
self._groups = actor_proxy_input.get('apifyProxyGroups', []) or []
|
|
218
231
|
self._country_code = actor_proxy_input.get('apifyProxyCountry')
|
|
219
232
|
self._subdivision_code = actor_proxy_input.get('apifyProxySubdivision')
|
|
@@ -291,7 +304,7 @@ class ProxyConfiguration:
|
|
|
291
304
|
)
|
|
292
305
|
if has_real_custom and (self._groups or self._country_code or self._subdivision_code or password):
|
|
293
306
|
raise ValueError(
|
|
294
|
-
'Cannot combine custom proxies with
|
|
307
|
+
'Cannot combine custom proxies with Scrapely Proxy! It is not allowed to set '
|
|
295
308
|
'"proxy_urls" or "new_url_function" combined with "groups", "country_code" '
|
|
296
309
|
'or "subdivision_code".'
|
|
297
310
|
)
|
|
@@ -480,7 +493,8 @@ class ProxyConfiguration:
|
|
|
480
493
|
Sends ``GET {status_url}/?format=json`` *through the platform gateway``
|
|
481
494
|
so the proxy handshake itself authenticates the run's credentials.
|
|
482
495
|
Mirrors the JS SDK: 4s timeout, 2 attempts, returns ``None`` when the
|
|
483
|
-
endpoint stays unreachable. Never raises.
|
|
496
|
+
endpoint stays unreachable. Never raises. ``fromPayload`` is True when
|
|
497
|
+
the gateway answered with its own status JSON rather than an HTTP error.
|
|
484
498
|
"""
|
|
485
499
|
import urllib.error
|
|
486
500
|
import urllib.request
|
|
@@ -514,6 +528,7 @@ class ProxyConfiguration:
|
|
|
514
528
|
'connected': payload.get('connected') is True,
|
|
515
529
|
'connectionError': payload.get('connectionError') or '',
|
|
516
530
|
'isManInTheMiddle': payload.get('isManInTheMiddle') is True,
|
|
531
|
+
'fromPayload': True,
|
|
517
532
|
}
|
|
518
533
|
except urllib.error.HTTPError as e:
|
|
519
534
|
if e.code == 407:
|
|
@@ -524,6 +539,7 @@ class ProxyConfiguration:
|
|
|
524
539
|
'SCRAPELY_PROXY_*/APIFY_PROXY_* credentials.'
|
|
525
540
|
),
|
|
526
541
|
'isManInTheMiddle': False,
|
|
542
|
+
'fromPayload': False,
|
|
527
543
|
}
|
|
528
544
|
if 590 <= e.code <= 599:
|
|
529
545
|
description = PROXY_STATUS_CODE_DESCRIPTIONS.get(e.code, 'upstream error')
|
|
@@ -531,6 +547,7 @@ class ProxyConfiguration:
|
|
|
531
547
|
'connected': False,
|
|
532
548
|
'connectionError': f'Platform proxy upstream error (HTTP {e.code}: {description}).',
|
|
533
549
|
'isManInTheMiddle': False,
|
|
550
|
+
'fromPayload': False,
|
|
534
551
|
}
|
|
535
552
|
# Other HTTP errors behave like connection errors: retry.
|
|
536
553
|
except Exception: # noqa: BLE001 - retry connection errors (Apify behavior)
|
|
@@ -623,6 +640,9 @@ class ProxyConfiguration:
|
|
|
623
640
|
|
|
624
641
|
Raises:
|
|
625
642
|
ValueError: If proxy configuration is invalid
|
|
643
|
+
ConnectionError: If the platform proxy reports that this configuration
|
|
644
|
+
cannot be used (for example an unknown proxy group), with the
|
|
645
|
+
proxy's ``connectionError`` as the message (Apify parity)
|
|
626
646
|
"""
|
|
627
647
|
if self._initialized:
|
|
628
648
|
return
|
|
@@ -639,9 +659,10 @@ class ProxyConfiguration:
|
|
|
639
659
|
if self._proxy_urls and self._new_url_function:
|
|
640
660
|
raise ValueError('Cannot specify both proxy_urls and new_url_function')
|
|
641
661
|
|
|
642
|
-
# Apify-parity access check
|
|
643
|
-
#
|
|
644
|
-
#
|
|
662
|
+
# Apify-parity access check: GET {status_url}/?format=json *through the
|
|
663
|
+
# platform gateway* so the proxy handshake itself authenticates the run's
|
|
664
|
+
# credentials. A "connected": false answer raises like Apify's
|
|
665
|
+
# _check_access; timeouts and HTTP errors only warn. Parity with the
|
|
645
666
|
# Apify SDKs (usesApifyProxy / uses_apify_proxy) and the JS Scrapely
|
|
646
667
|
# port (platformEnv): the check only applies when this configuration
|
|
647
668
|
# actually uses the platform proxy — BYOP-only configurations skip it
|
|
@@ -662,6 +683,8 @@ class ProxyConfiguration:
|
|
|
662
683
|
)
|
|
663
684
|
else:
|
|
664
685
|
self._is_man_in_the_middle = status['isManInTheMiddle']
|
|
686
|
+
if not status['connected'] and status['fromPayload']:
|
|
687
|
+
raise ConnectionError(status['connectionError'])
|
|
665
688
|
if not status['connected']:
|
|
666
689
|
logger.warning('Platform proxy access check failed: %s', status['connectionError'])
|
|
667
690
|
else:
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scrapely-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Scrapely Python SDK - A toolkit for building Actors on the Scrapely platform
|
|
5
5
|
Author-email: Scrapely <support@scrape.ly>
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://scrape.ly
|
|
8
8
|
Project-URL: Documentation, https://docs.scrape.ly
|
|
9
9
|
Project-URL: Repository, https://github.com/SandeepDev1/scrapely
|
|
10
10
|
Keywords: scraping,actor,serverless,apify,scrapely
|
|
11
11
|
Classifier: Development Status :: 3 - Alpha
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
13
|
Classifier: Operating System :: OS Independent
|
|
15
14
|
Classifier: Programming Language :: Python :: 3
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -22,7 +21,7 @@ Description-Content-Type: text/markdown
|
|
|
22
21
|
Requires-Dist: httpx>=0.25.0
|
|
23
22
|
Requires-Dist: crawlee<2.0.0,>=1.8.0
|
|
24
23
|
Requires-Dist: websockets>=12.0
|
|
25
|
-
Requires-Dist: scrapely-client-python<2,>=1.0.
|
|
24
|
+
Requires-Dist: scrapely-client-python<2,>=1.0.2
|
|
26
25
|
Requires-Dist: pydantic>=2.0.0
|
|
27
26
|
Requires-Dist: cryptography>=42.0.0
|
|
28
27
|
Provides-Extra: dev
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
scrapely/__init__.py,sha256=
|
|
1
|
+
scrapely/__init__.py,sha256=qBRp9YUgItQprvVgBepI-5VWc6OuQ2LQjCdSp5Xviv0,5745
|
|
2
2
|
scrapely/_browser_locations.py,sha256=8QVVnkms2bDn3_LDYgYDR59MvFx8757l_JYnZ7xb3oI,145269
|
|
3
|
-
scrapely/actor.py,sha256=
|
|
3
|
+
scrapely/actor.py,sha256=wNifzbcvfH3wt0-aSaesJN_uZXNREhNfTgiORDGz7IM,75081
|
|
4
4
|
scrapely/browser_configuration.py,sha256=jAZapNSHLFW05B96df4wNBtcyiiU9DA3ml0yTL1FwfA,5770
|
|
5
5
|
scrapely/charging.py,sha256=ckqsGbGuzpdw5PgEt6Apx3cwR46sA8V2WwUDSqSSfz8,46887
|
|
6
6
|
scrapely/charging_client.py,sha256=tDtlPwLSVFvoRa2NBgNjyfHkAUM77SSIyYT2ACgpDm8,3710
|
|
@@ -9,7 +9,7 @@ scrapely/errors.py,sha256=DCAlVvE3jToZuPEG2HVDL7i8D8hLboomQ5JpSOcsXoQ,4107
|
|
|
9
9
|
scrapely/log.py,sha256=iVKoQdMLQXEWz1STzbSXrNUxsS_FNwiIdqoEg7CzeqU,1440
|
|
10
10
|
scrapely/patch_browser_pool.py,sha256=sRAjuzKoOEgn0X8695c_9qioRALzkTWXoDlorVnK9jg,37426
|
|
11
11
|
scrapely/pricing_types.py,sha256=J6wCddZd-U1MYeOtaaEdeHSsp1HBgiQLfvSInBOvhIo,5305
|
|
12
|
-
scrapely/proxy_configuration.py,sha256=
|
|
12
|
+
scrapely/proxy_configuration.py,sha256=i6icNhei8deVK9KgCC_hdeTuSawh8WiMUOrgjvkps1s,35845
|
|
13
13
|
scrapely/py.typed,sha256=XtWlywqUjc6x_M2gAXlDtYXbAsYkCjKZEjFyJvaHvm4,105
|
|
14
14
|
scrapely/types.py,sha256=ceVuHvhYbPai2CL3vFmXLTMoGaMR27IefJFgWhzd7ws,8900
|
|
15
15
|
scrapely/webhook.py,sha256=4WOM42ypOSOs4lKtP5ofxAh6lX-7c8p5U9lOfuFzx-g,2830
|
|
@@ -37,7 +37,7 @@ scrapely/storage/_file_system/_key_value_store_client.py,sha256=4Ipgbh4qEqAkwQJ9
|
|
|
37
37
|
scrapely/storage/_file_system/_storage_client.py,sha256=aaGJ4P0QAtIgEMl6yHpuobeWFD5M4rMC5HwhOOvuGuo,1794
|
|
38
38
|
scrapely/storage_clients/__init__.py,sha256=cs1YX7JV2_GKBXeiiJgkQFali08b5_y_jH6Yw7iROAA,656
|
|
39
39
|
scrapely/storages/__init__.py,sha256=4PAELhJNZ3exJ7Uouicn-bCQ58NafKjhLBixQ8ATQc4,173
|
|
40
|
-
scrapely_python-0.1.
|
|
41
|
-
scrapely_python-0.1.
|
|
42
|
-
scrapely_python-0.1.
|
|
43
|
-
scrapely_python-0.1.
|
|
40
|
+
scrapely_python-0.1.2.dist-info/METADATA,sha256=j0LJpAvLe7XGLBaGlKJkCq564TBZewFlGUDPgCbTB5U,3104
|
|
41
|
+
scrapely_python-0.1.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
42
|
+
scrapely_python-0.1.2.dist-info/top_level.txt,sha256=T4B9UMrs4q99QSAFyD1zdXhl40KnLhBKqrDkPhOTsmY,9
|
|
43
|
+
scrapely_python-0.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|