refactor: replace's Selector inpt (text/body) with 1 argument called content

This commit is contained in:
Karim shoair
2025-07-29 06:08:15 +03:00
parent 9e9ba9ab10
commit b9c7a5af2e
5 changed files with 30 additions and 42 deletions
+1 -1
View File
@@ -80,7 +80,7 @@ def test_scrapling():
@benchmark @benchmark
def test_parsel(): def test_parsel():
return Selector(text=large_html).css(".item::text").extract() return Selector(content=large_html).css(".item::text").extract()
@benchmark @benchmark
+5 -10
View File
@@ -34,8 +34,7 @@ class ResponseFactory:
Response( Response(
url=current_request.url, url=current_request.url,
# using current_response.text() will trigger "Error: Response.text: Response body is unavailable for redirect responses" # using current_response.text() will trigger "Error: Response.text: Response body is unavailable for redirect responses"
text="", content="",
body=b"",
status=current_response.status if current_response else 301, status=current_response.status if current_response else 301,
reason=( reason=(
current_response.status_text current_response.status_text
@@ -112,8 +111,7 @@ class ResponseFactory:
return Response( return Response(
url=page.url, url=page.url,
text=page_content, content=page_content,
body=page_content.encode("utf-8"),
status=final_response.status, status=final_response.status,
reason=status_text, reason=status_text,
encoding=encoding, encoding=encoding,
@@ -141,8 +139,7 @@ class ResponseFactory:
Response( Response(
url=current_request.url, url=current_request.url,
# using current_response.text() will trigger "Error: Response.text: Response body is unavailable for redirect responses" # using current_response.text() will trigger "Error: Response.text: Response body is unavailable for redirect responses"
text="", content="",
body=b"",
status=current_response.status if current_response else 301, status=current_response.status if current_response else 301,
reason=( reason=(
current_response.status_text current_response.status_text
@@ -221,8 +218,7 @@ class ResponseFactory:
return Response( return Response(
url=page.url, url=page.url,
text=page_content, content=page_content,
body=page_content.encode("utf-8"),
status=final_response.status, status=final_response.status,
reason=status_text, reason=status_text,
encoding=encoding, encoding=encoding,
@@ -243,8 +239,7 @@ class ResponseFactory:
""" """
return Response( return Response(
url=response.url, url=response.url,
text=response.text, content=response.content
body=response.content
if type(response.content) is bytes if type(response.content) is bytes
else response.content.encode(), else response.content.encode(),
status=response.status_code, status=response.status_code,
+5 -5
View File
@@ -103,8 +103,7 @@ class Response(Selector):
def __init__( def __init__(
self, self,
url: str, url: str,
text: str, content: str | bytes,
body: bytes,
status: int, status: int,
reason: str, reason: str,
cookies: Union[Tuple[Dict[str, str], ...], Dict[str, str]], cookies: Union[Tuple[Dict[str, str], ...], Dict[str, str]],
@@ -122,10 +121,11 @@ class Response(Selector):
self.headers = headers self.headers = headers
self.request_headers = request_headers self.request_headers = request_headers
self.history = history or [] self.history = history or []
encoding = ResponseEncoding.get_value(encoding, text) encoding = ResponseEncoding.get_value(
encoding, content.decode("utf-8") if isinstance(content, bytes) else content
)
super().__init__( super().__init__(
text=text, content=content,
body=body,
url=adaptive_domain or url, url=adaptive_domain or url,
encoding=encoding, encoding=encoding,
**selector_config, **selector_config,
+15 -19
View File
@@ -50,9 +50,8 @@ class Selector(SelectorsGeneration):
def __init__( def __init__(
self, self,
text: Optional[str] = None, content: Optional[Union[str, bytes]] = None,
url: Optional[str] = None, url: Optional[str] = None,
body: bytes = b"",
encoding: str = "utf8", encoding: str = "utf8",
huge_tree: bool = True, huge_tree: bool = True,
root: Optional[html.HtmlElement] = None, root: Optional[html.HtmlElement] = None,
@@ -72,9 +71,8 @@ class Selector(SelectorsGeneration):
not possible. You can test it here and see code explodes with `AssertionError: invalid Element proxy at...`. not possible. You can test it here and see code explodes with `AssertionError: invalid Element proxy at...`.
It's an old issue with lxml, see `this entry <https://bugs.launchpad.net/lxml/+bug/736708>` It's an old issue with lxml, see `this entry <https://bugs.launchpad.net/lxml/+bug/736708>`
:param text: HTML body passed as text. :param content: HTML content as either string or bytes.
:param url: It allows storing a URL with the HTML data for retrieving later. :param url: It allows storing a URL with the HTML data for retrieving later.
:param body: HTML body as an ``bytes`` object. It can be used instead of the ``text`` argument.
:param encoding: The encoding type that will be used in HTML parsing, default is `UTF-8` :param encoding: The encoding type that will be used in HTML parsing, default is `UTF-8`
:param huge_tree: Enabled by default, should always be enabled when parsing large HTML documents. This controls :param huge_tree: Enabled by default, should always be enabled when parsing large HTML documents. This controls
the libxml2 feature that forbids parsing certain large documents to protect from possible memory exhaustion. the libxml2 feature that forbids parsing certain large documents to protect from possible memory exhaustion.
@@ -88,28 +86,24 @@ class Selector(SelectorsGeneration):
:param storage_args: A dictionary of ``argument->value`` pairs to be passed for the storage class. :param storage_args: A dictionary of ``argument->value`` pairs to be passed for the storage class.
If empty, default values will be used. If empty, default values will be used.
""" """
if root is None and not body and text is None: if root is None and content is None:
raise ValueError( raise ValueError(
"Selector class needs text, body, or root arguments to work" "Selector class needs HTML content, or root arguments to work"
) )
self.__text = "" self.__text = ""
if root is None: if root is None:
if text is None: if isinstance(content, bytes):
if not body or not isinstance(body, bytes): body = content.replace(b"\x00", b"").strip()
raise TypeError( elif isinstance(content, str):
f"body argument must be valid and of type bytes, got {body.__class__}" body = (
content.strip().replace("\x00", "").encode(encoding) or b"<html/>"
) )
body = body.replace(b"\x00", b"").strip()
else: else:
if not isinstance(text, str):
raise TypeError( raise TypeError(
f"text argument must be of type str, got {text.__class__}" f"content argument must be str or bytes, got {type(content)}"
) )
body = text.strip().replace("\x00", "").encode(encoding) or b"<html/>"
# https://lxml.de/api/lxml.etree.HTMLParser-class.html # https://lxml.de/api/lxml.etree.HTMLParser-class.html
parser = html.HTMLParser( parser = html.HTMLParser(
recover=True, recover=True,
@@ -122,8 +116,10 @@ class Selector(SelectorsGeneration):
strip_cdata=(not keep_cdata), strip_cdata=(not keep_cdata),
) )
self._root = etree.fromstring(body, parser=parser, base_url=url) self._root = etree.fromstring(body, parser=parser, base_url=url)
if is_jsonable(text or body.decode()):
self.__text = TextHandler(text or body.decode()) jsonable_text = content if isinstance(content, str) else body.decode()
if is_jsonable(jsonable_text):
self.__text = TextHandler(jsonable_text)
else: else:
# All HTML types inherit from HtmlMixin so this to check for all at once # All HTML types inherit from HtmlMixin so this to check for all at once
@@ -930,7 +926,7 @@ class Selector(SelectorsGeneration):
) -> None: ) -> None:
"""Saves the element's unique properties to the storage for retrieval and relocation later """Saves the element's unique properties to the storage for retrieval and relocation later
:param element: The element itself that we want to save to storage, it can be an ` Selector ` or pure ` HtmlElement ` :param element: The element itself that we want to save to storage, it can be a ` Selector ` or pure ` HtmlElement `
:param identifier: This is the identifier that will be used to retrieve the element later from the storage. See :param identifier: This is the identifier that will be used to retrieve the element later from the storage. See
the docs for more info. the docs for more info.
""" """
+1 -4
View File
@@ -173,10 +173,7 @@ class TestErrorHandling:
_ = Selector(root="ayo", adaptive=False) _ = Selector(root="ayo", adaptive=False)
with pytest.raises(TypeError): with pytest.raises(TypeError):
_ = Selector(text=1, adaptive=False) _ = Selector(content=1, adaptive=False)
with pytest.raises(TypeError):
_ = Selector(body=1, adaptive=False)
def test_invalid_storage(self, page, html_content): def test_invalid_storage(self, page, html_content):
"""Test invalid storage parameter""" """Test invalid storage parameter"""