scrapely-python 0.1.33__py3-none-any.whl → 0.1.34__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.33'
230
+ __version__ = '0.1.34'
231
231
  __all__ = [
232
232
  # Core
233
233
  'Actor',
scrapely/actor.py CHANGED
@@ -285,6 +285,53 @@ class _ActorMeta(type):
285
285
  await cls._shutdown()
286
286
 
287
287
 
288
+
289
+ def _element_markup(obj: Any) -> Optional[str]:
290
+ """The markup of a parsed XML/HTML element, or None when ``obj`` is not one.
291
+
292
+ Debug captures store the source an item was read from. Feed and page parsers hand over elements
293
+ (xml.etree / lxml elements and trees, BeautifulSoup tags, selectolax nodes, parsel / Scrapy
294
+ selectors); their ``str()`` is only a repr such as ``<Element 'change' at 0x...>``, which shows
295
+ nothing of the source. Optional parsers are recognised by module, never imported here.
296
+ """
297
+ import xml.etree.ElementTree as ET
298
+
299
+ if isinstance(obj, ET.ElementTree):
300
+ root = obj.getroot()
301
+ return ET.tostring(root, encoding='unicode') if root is not None else ''
302
+ if isinstance(obj, ET.Element):
303
+ if obj.tail: # the text after the element belongs to its parent, as with lxml's with_tail=False
304
+ import copy
305
+
306
+ obj = copy.copy(obj)
307
+ obj.tail = None
308
+ return ET.tostring(obj, encoding='unicode')
309
+ modules = [getattr(c, '__module__', '') or '' for c in type(obj).__mro__]
310
+ if any(m == 'lxml' or m.startswith('lxml.') for m in modules):
311
+ from lxml import etree # type: ignore[import-not-found]
312
+
313
+ out = etree.tostring(obj, encoding='unicode', with_tail=False)
314
+ return out if isinstance(out, str) else None
315
+ if any(m == 'bs4' or m.startswith('bs4.') for m in modules):
316
+ return str(obj)
317
+ if any(m == 'selectolax' or m.startswith('selectolax.') for m in modules):
318
+ html = getattr(obj, 'html', None)
319
+ return html if isinstance(html, str) else None
320
+ if any(m == 'parsel' or m.startswith('parsel.') for m in modules) and callable(getattr(obj, 'get', None)):
321
+ out = obj.get()
322
+ return out if isinstance(out, str) else None
323
+ return None
324
+
325
+
326
+ def _capture_json_default(obj: Any) -> Any:
327
+ """json.dumps fallback for debug captures: elements as their markup, anything else as str()."""
328
+ try:
329
+ markup = _element_markup(obj)
330
+ except Exception: # an unusual node type must not lose the rest of the payload
331
+ markup = None
332
+ return markup if markup is not None else str(obj)
333
+
334
+
288
335
  class Actor(metaclass=_ActorMeta):
289
336
  """Core class for building Actors on the Scrapely platform."""
290
337
 
@@ -974,12 +1021,15 @@ class Actor(metaclass=_ActorMeta):
974
1021
  if cls._debug_capture_count >= cls._DEBUG_CAPTURE_MAX_RECORDS:
975
1022
  return
976
1023
  try:
977
- if isinstance(payload, (bytes, bytearray)):
1024
+ markup = _element_markup(payload)
1025
+ if markup is not None:
1026
+ text = markup
1027
+ elif isinstance(payload, (bytes, bytearray)):
978
1028
  text = bytes(payload).decode('utf-8', 'replace')
979
1029
  elif isinstance(payload, str):
980
1030
  text = payload
981
1031
  else:
982
- text = json.dumps(payload, default=str, ensure_ascii=False, separators=(',', ':'))
1032
+ text = json.dumps(payload, default=_capture_json_default, ensure_ascii=False, separators=(',', ':'))
983
1033
  record = cls._debug_capture_record(str(key), text)
984
1034
  cls._debug_capture_count += 1
985
1035
  await cls.set_value(cls.debug_capture_record_key(key), record)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scrapely-python
3
- Version: 0.1.33
3
+ Version: 0.1.34
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=IewlIO5DRdcqs_defs-pLoIJ_u78Vb7J7Ix0uMhV-ds,9668
1
+ scrapely/__init__.py,sha256=5lWyf7hHuDQXopBE8Koi2joSWNZWECE2DvPKu7CvkX0,9668
2
2
  scrapely/_browser_locations.py,sha256=8QVVnkms2bDn3_LDYgYDR59MvFx8757l_JYnZ7xb3oI,145269
3
- scrapely/actor.py,sha256=mnQ0zYCsRJEAipxH1Sdg6o6_r2gEAvnuA1r5M3AsEgo,89122
3
+ scrapely/actor.py,sha256=DmDnwPSmqmdEq_HIBgW0JivKl-8FrUBhNKNGXYh6QGA,91407
4
4
  scrapely/browser_configuration.py,sha256=jAZapNSHLFW05B96df4wNBtcyiiU9DA3ml0yTL1FwfA,5770
5
5
  scrapely/cdp_retry.py,sha256=hdTJEAIKhApM7IOk1DrQEl8NoflSJSmqd4RbNJ2IsgI,8540
6
6
  scrapely/charging.py,sha256=osi2Ys99IyIJwFj0eRtV_RshokPs4mA2CE7oftNbN58,95559
@@ -55,10 +55,10 @@ scrapely/storage/_file_system/_key_value_store_client.py,sha256=1C22qY-RWlARzMyW
55
55
  scrapely/storage/_file_system/_storage_client.py,sha256=QHa3IULEb6AmgRFwpBqHD2OUTmtA_gl3lId-j6QdSFo,2027
56
56
  scrapely/storage_clients/__init__.py,sha256=q2pC-Rwe-BfHl018OyN_P0EDSx4-hNdxbk3pOCLLd4U,618
57
57
  scrapely/storages/__init__.py,sha256=kDNtd9VodjdHd4u7GzruJo0aB2HeiBN2_-HrkeqjmFg,139
58
- scrapely_python-0.1.33.dist-info/licenses/LICENSE,sha256=pQGY0XwjIYh-a2HfawxHq1en53bXkFkDj2Q70jKWHr4,1088
59
- scrapely_python-0.1.33.dist-info/licenses/LICENSE-APACHE,sha256=N8m0FzMq8i4UbTgXZvKEZJSD-27OF8pWZYS_atz4kTU,11355
60
- scrapely_python-0.1.33.dist-info/licenses/NOTICE,sha256=1ltrzKk2ez7x2BPNpXeyvOlTty5AfABYDBlwNilpORk,788
61
- scrapely_python-0.1.33.dist-info/METADATA,sha256=hhDlcnp5JyIcT9C_-kawPgmqmGanpQgX3TJJqcCQl7s,3582
62
- scrapely_python-0.1.33.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
63
- scrapely_python-0.1.33.dist-info/top_level.txt,sha256=T4B9UMrs4q99QSAFyD1zdXhl40KnLhBKqrDkPhOTsmY,9
64
- scrapely_python-0.1.33.dist-info/RECORD,,
58
+ scrapely_python-0.1.34.dist-info/licenses/LICENSE,sha256=pQGY0XwjIYh-a2HfawxHq1en53bXkFkDj2Q70jKWHr4,1088
59
+ scrapely_python-0.1.34.dist-info/licenses/LICENSE-APACHE,sha256=N8m0FzMq8i4UbTgXZvKEZJSD-27OF8pWZYS_atz4kTU,11355
60
+ scrapely_python-0.1.34.dist-info/licenses/NOTICE,sha256=1ltrzKk2ez7x2BPNpXeyvOlTty5AfABYDBlwNilpORk,788
61
+ scrapely_python-0.1.34.dist-info/METADATA,sha256=f84Jwc8hXM7jbHFipBYO1dlAojlHDxBpm1B8-BNmeYY,3582
62
+ scrapely_python-0.1.34.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
63
+ scrapely_python-0.1.34.dist-info/top_level.txt,sha256=T4B9UMrs4q99QSAFyD1zdXhl40KnLhBKqrDkPhOTsmY,9
64
+ scrapely_python-0.1.34.dist-info/RECORD,,