scrapely-python 0.1.23__py3-none-any.whl → 0.1.25__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 CHANGED
@@ -227,7 +227,7 @@ if TYPE_CHECKING: # Static analysers and IDEs see the lazy names as plain impor
227
227
  set_api_storage,
228
228
  )
229
229
 
230
- __version__ = '0.1.23'
230
+ __version__ = '0.1.25'
231
231
  __all__ = [
232
232
  # Core
233
233
  'Actor',
scrapely/actor.py CHANGED
@@ -360,6 +360,7 @@ class Actor(metaclass=_ActorMeta):
360
360
 
361
361
  @classmethod
362
362
  async def init(cls, options: Optional[InitOptions] = None) -> None:
363
+ cls._main_loop = asyncio.get_running_loop() # for debug_capture_sync from worker threads
363
364
  if cls._initialized:
364
365
  return
365
366
 
@@ -922,6 +923,59 @@ class Actor(metaclass=_ActorMeta):
922
923
  cls._default_kvs = await cls.open_key_value_store()
923
924
  await cls._default_kvs.set_value(key, value, content_type=content_type)
924
925
 
926
+ # Debug capture: raw source payloads for platform diagnostics. Only active when the platform
927
+ # sets SCRAPELY_DEBUG_CAPTURE=1 on its own diagnostic runs; a no-op in every other run.
928
+ _DEBUG_CAPTURE_MAX_BYTES = 256 * 1024
929
+ _DEBUG_CAPTURE_MAX_RECORDS = 500
930
+ _debug_capture_count = 0
931
+
932
+ @staticmethod
933
+ def debug_capture_record_key(key: str) -> str:
934
+ """The key-value store record name for a capture key (item URL or id)."""
935
+ import hashlib
936
+ return 'DEBUG_CAPTURE_' + hashlib.sha1(str(key).strip().encode('utf-8')).hexdigest()[:24]
937
+
938
+ _main_loop: Optional[asyncio.AbstractEventLoop] = None
939
+
940
+ @classmethod
941
+ def debug_capture_sync(cls, key: str, payload: Any) -> None:
942
+ """debug_capture for synchronous code (also from worker threads, e.g. asyncio.to_thread).
943
+ Does nothing unless the platform enabled capture for this run."""
944
+ if os.environ.get('SCRAPELY_DEBUG_CAPTURE') != '1' or not key:
945
+ return
946
+ loop = cls._main_loop
947
+ if loop is None or loop.is_closed():
948
+ return
949
+ try:
950
+ try:
951
+ running = asyncio.get_running_loop()
952
+ except RuntimeError:
953
+ running = None
954
+ if running is loop:
955
+ loop.create_task(cls.debug_capture(key, payload))
956
+ else:
957
+ asyncio.run_coroutine_threadsafe(cls.debug_capture(key, payload), loop).result(timeout=15)
958
+ except Exception as err: # never break the run
959
+ logging.getLogger('scrapely').debug('debug_capture_sync failed: %s', err)
960
+
961
+ @classmethod
962
+ async def debug_capture(cls, key: str, payload: Any) -> None:
963
+ """Save the raw source payload (API JSON or HTML) an item was extracted from, keyed by
964
+ the item's URL or id. Does nothing unless the platform enabled capture for this run."""
965
+ if os.environ.get('SCRAPELY_DEBUG_CAPTURE') != '1' or not key:
966
+ return
967
+ if cls._debug_capture_count >= cls._DEBUG_CAPTURE_MAX_RECORDS:
968
+ return
969
+ try:
970
+ text = payload if isinstance(payload, str) else json.dumps(payload, default=str, ensure_ascii=False)
971
+ if isinstance(text, bytes):
972
+ text = text.decode('utf-8', 'replace')
973
+ text = text[: cls._DEBUG_CAPTURE_MAX_BYTES]
974
+ cls._debug_capture_count += 1
975
+ await cls.set_value(cls.debug_capture_record_key(key), {'key': str(key), 'payload': text})
976
+ except Exception as err: # never break the run
977
+ logging.getLogger('scrapely').debug('debug_capture failed: %s', err)
978
+
925
979
  @classmethod
926
980
  def _resolve_open_args(
927
981
  cls,
@@ -203,7 +203,7 @@ def _platform_missing_error() -> str:
203
203
  'proxy credentials (SCRAPELY_PROXY_*/APIFY_PROXY_*) were injected into this '
204
204
  'environment. Run the actor on Scrapely with proxy access enabled, or remove the '
205
205
  'platform proxy entries. To use the platform proxy locally or from outside '
206
- 'Scrapely, copy your proxy credentials from the Scrapely console (Proxy page) '
206
+ 'Scrapely, copy your proxy credentials from the Scrapely dashboard (Proxy page) '
207
207
  'into the SCRAPELY_PROXY_HOSTNAME, SCRAPELY_PROXY_PORT, SCRAPELY_PROXY_USERNAME '
208
208
  'and SCRAPELY_PROXY_PASSWORD environment variables (or pass password=... '
209
209
  'explicitly). External platform proxy access requires an active plan or a '
@@ -431,7 +431,7 @@ class ProxyConfiguration:
431
431
  logger.warning(
432
432
  'The Scrapely platform proxy is only available in on-platform runs when your '
433
433
  'account has proxy access. Locally, either add proxy_urls to your actor input, '
434
- 'or copy your proxy credentials from the Scrapely console (Proxy page) into the '
434
+ 'or copy your proxy credentials from the Scrapely dashboard (Proxy page) into the '
435
435
  'SCRAPELY_PROXY_HOSTNAME, SCRAPELY_PROXY_PORT, SCRAPELY_PROXY_USERNAME and '
436
436
  'SCRAPELY_PROXY_PASSWORD environment variables. Using the platform proxy from '
437
437
  'outside Scrapely requires an active plan or a prepaid (PAYG) balance of at '
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scrapely-python
3
- Version: 0.1.23
3
+ Version: 0.1.25
4
4
  Summary: Scrapely Python SDK - A toolkit for building Actors on the Scrapely platform
5
5
  Author-email: Scrapely <support@scrape.ly>
6
6
  License-Expression: MIT AND Apache-2.0
@@ -1,6 +1,6 @@
1
- scrapely/__init__.py,sha256=urKlJYfwafwGuUNCaDEPM0wK5H_krI8jYNKys8dsh9U,9661
1
+ scrapely/__init__.py,sha256=2-L8VbKFypsPs_hl-GqCOKYwQvVcifUL4iXm0fOqhP4,9661
2
2
  scrapely/_browser_locations.py,sha256=8QVVnkms2bDn3_LDYgYDR59MvFx8757l_JYnZ7xb3oI,145269
3
- scrapely/actor.py,sha256=fNxlB2KxattbBh6aMo1p_ALrDQ3GdbJa03w_6DgIkjs,85310
3
+ scrapely/actor.py,sha256=1SBHZYBRLmU4k_erjKL7yRfP0EgR7ZKXefAK8wWKRz0,88004
4
4
  scrapely/browser_configuration.py,sha256=jAZapNSHLFW05B96df4wNBtcyiiU9DA3ml0yTL1FwfA,5770
5
5
  scrapely/cdp_retry.py,sha256=hdTJEAIKhApM7IOk1DrQEl8NoflSJSmqd4RbNJ2IsgI,8540
6
6
  scrapely/charging.py,sha256=ns-kuUJtl8hBI4-3iBisC62BggJG67yYOa1yVVItoUI,83800
@@ -10,7 +10,7 @@ scrapely/errors.py,sha256=DCAlVvE3jToZuPEG2HVDL7i8D8hLboomQ5JpSOcsXoQ,4107
10
10
  scrapely/log.py,sha256=iVKoQdMLQXEWz1STzbSXrNUxsS_FNwiIdqoEg7CzeqU,1440
11
11
  scrapely/patch_browser_pool.py,sha256=R-1QDKa-ZvfdQ9wgyA8JCtmQqD9v7dOsaCZnSUoyf7U,42214
12
12
  scrapely/pricing_types.py,sha256=J6wCddZd-U1MYeOtaaEdeHSsp1HBgiQLfvSInBOvhIo,5305
13
- scrapely/proxy_configuration.py,sha256=4passyiUBfBsuzaBDyf5003rY0ayLYwwSnev0FaxCs0,38125
13
+ scrapely/proxy_configuration.py,sha256=mQhUjXnqM1PcUrnRmXxVWdLnlaRlb8Je4K2j54lC6YQ,38129
14
14
  scrapely/py.typed,sha256=XtWlywqUjc6x_M2gAXlDtYXbAsYkCjKZEjFyJvaHvm4,105
15
15
  scrapely/solver.py,sha256=ahuORJwZu9aJzCKWF14qTjQSnwT6dEbhMophQQ03JHw,35854
16
16
  scrapely/types.py,sha256=ceVuHvhYbPai2CL3vFmXLTMoGaMR27IefJFgWhzd7ws,8900
@@ -39,10 +39,10 @@ scrapely/storage/_file_system/_key_value_store_client.py,sha256=4Ipgbh4qEqAkwQJ9
39
39
  scrapely/storage/_file_system/_storage_client.py,sha256=aaGJ4P0QAtIgEMl6yHpuobeWFD5M4rMC5HwhOOvuGuo,1794
40
40
  scrapely/storage_clients/__init__.py,sha256=cs1YX7JV2_GKBXeiiJgkQFali08b5_y_jH6Yw7iROAA,656
41
41
  scrapely/storages/__init__.py,sha256=4PAELhJNZ3exJ7Uouicn-bCQ58NafKjhLBixQ8ATQc4,173
42
- scrapely_python-0.1.23.dist-info/licenses/LICENSE,sha256=pQGY0XwjIYh-a2HfawxHq1en53bXkFkDj2Q70jKWHr4,1088
43
- scrapely_python-0.1.23.dist-info/licenses/LICENSE-APACHE,sha256=N8m0FzMq8i4UbTgXZvKEZJSD-27OF8pWZYS_atz4kTU,11355
44
- scrapely_python-0.1.23.dist-info/licenses/NOTICE,sha256=DEsNrw6UdSIqdYYqMWVFgJnyt6jUivTD7iFU6YM_e1A,1122
45
- scrapely_python-0.1.23.dist-info/METADATA,sha256=AY2FmyNKFInqRuBqZ2HnLSUVjz-UZ4bKeRTV5OsgPTU,3487
46
- scrapely_python-0.1.23.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
47
- scrapely_python-0.1.23.dist-info/top_level.txt,sha256=T4B9UMrs4q99QSAFyD1zdXhl40KnLhBKqrDkPhOTsmY,9
48
- scrapely_python-0.1.23.dist-info/RECORD,,
42
+ scrapely_python-0.1.25.dist-info/licenses/LICENSE,sha256=pQGY0XwjIYh-a2HfawxHq1en53bXkFkDj2Q70jKWHr4,1088
43
+ scrapely_python-0.1.25.dist-info/licenses/LICENSE-APACHE,sha256=N8m0FzMq8i4UbTgXZvKEZJSD-27OF8pWZYS_atz4kTU,11355
44
+ scrapely_python-0.1.25.dist-info/licenses/NOTICE,sha256=DEsNrw6UdSIqdYYqMWVFgJnyt6jUivTD7iFU6YM_e1A,1122
45
+ scrapely_python-0.1.25.dist-info/METADATA,sha256=9aB0qUPYZPSoRBuYQUIWWFQRnLrxpG66yP_ES6q_ZG8,3487
46
+ scrapely_python-0.1.25.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
47
+ scrapely_python-0.1.25.dist-info/top_level.txt,sha256=T4B9UMrs4q99QSAFyD1zdXhl40KnLhBKqrDkPhOTsmY,9
48
+ scrapely_python-0.1.25.dist-info/RECORD,,