Skip to content

Response Class

The Response class wraps HTTP responses returned by all fetchers, providing access to status, headers, body, cookies, and a Selector for parsing.

You can import the Response class like below:

from scrapling.engines.toolbelt.custom import Response

scrapling.engines.toolbelt.custom.Response

Response(
    url,
    content,
    status,
    reason,
    cookies,
    headers,
    request_headers,
    encoding="utf-8",
    method="GET",
    history=None,
    meta=None,
    **selector_config
)

Bases: Selector


              flowchart TD
              scrapling.engines.toolbelt.custom.Response[Response]
              scrapling.parser.Selector[Selector]
              scrapling.core.mixins.SelectorsGeneration[SelectorsGeneration]

                              scrapling.parser.Selector --> scrapling.engines.toolbelt.custom.Response
                                scrapling.core.mixins.SelectorsGeneration --> scrapling.parser.Selector
                



              click scrapling.engines.toolbelt.custom.Response href "" "scrapling.engines.toolbelt.custom.Response"
              click scrapling.parser.Selector href "" "scrapling.parser.Selector"
              click scrapling.core.mixins.SelectorsGeneration href "" "scrapling.core.mixins.SelectorsGeneration"
            

This class is returned by all engines as a way to unify the response type between different libraries.

PARAMETER DESCRIPTION
status

HTTP status code.

TYPE: int

reason

HTTP status message.

TYPE: str

cookies

Response cookies.

TYPE: Tuple[Dict[str, str], ...] | Dict[str, str]

headers

Response headers.

TYPE: Dict

request_headers

Request headers sent with the request.

TYPE: Dict

history

List of redirect responses, if any.

TYPE: List | None DEFAULT: None

meta

Metadata dictionary (e.g., proxy used).

TYPE: Dict[str, Any] | None DEFAULT: None

request

Associated spider Request object (set by crawler, in the spiders framework).

captured_xhr

List of captured XHR/fetch Response objects. Populated when capture_xhr is set on a browser session.

The main class that works as a wrapper for the HTML input data. Using this class, you can search for elements with expressions in CSS, XPath, or with simply text. Check the docs for more info.

Here we try to extend module lxml.html.HtmlElement while maintaining a simpler interface, We are not inheriting from the lxml.html.HtmlElement because it's not pickleable, which makes a lot of reference jobs 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>

PARAMETER DESCRIPTION
content

HTML content as either string or bytes.

TYPE: str | bytes

url

It allows storing a URL with the HTML data for retrieving later.

TYPE: str

encoding

The encoding type that will be used in HTML parsing, default is UTF-8

TYPE: str DEFAULT: 'utf-8'

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.

root

Used internally to pass etree objects instead of text/body arguments, it takes the highest priority. Don't use it unless you know what you are doing!

keep_comments

While parsing the HTML body, drop comments or not. Disabled by default for obvious reasons

keep_cdata

While parsing the HTML body, drop cdata or not. Disabled by default for cleaner HTML.

adaptive

Globally turn off the adaptive feature in all functions, this argument takes higher priority over all adaptive related arguments/functions in the class.

storage

The storage class to be passed for adaptive functionalities, see Docs for more info.

storage_args

A dictionary of argument->value pairs to be passed for the storage class. If empty, default values will be used.

Source code in scrapling/engines/toolbelt/custom.py
def __init__(
    self,
    url: str,
    content: str | bytes,
    status: int,
    reason: str,
    cookies: Tuple[Dict[str, str], ...] | Dict[str, str],
    headers: Dict,
    request_headers: Dict,
    encoding: str = "utf-8",
    method: str = "GET",
    history: List | None = None,
    meta: Dict[str, Any] | None = None,
    **selector_config: Any,
):
    if isinstance(content, str):
        content = content.encode("utf-8")

    adaptive_domain: str = cast(str, selector_config.pop("adaptive_domain", ""))
    self.status = status
    self.reason = reason
    self.cookies = cookies
    self.headers = headers
    self.request_headers = request_headers
    self.history = history or []
    super().__init__(
        content=content,
        url=adaptive_domain or url,
        encoding=encoding,
        **selector_config,
    )
    # For easier debugging while working from a Python shell
    log.info(f"Fetched ({status}) <{method} {url}> (referer: {request_headers.get('referer')})")

    if meta and not isinstance(meta, dict):
        raise TypeError(f"Response meta should be dictionary but got {type(meta).__name__} instead!")

    self.meta: Dict[str, Any] = meta or {}
    self.request: Optional["Request"] = None  # Will be set by crawler
    self.captured_xhr: List["Response"] = []

status instance-attribute

status = status

reason instance-attribute

reason = reason

cookies instance-attribute

cookies = cookies

headers instance-attribute

headers = headers

request_headers instance-attribute

request_headers = request_headers

history instance-attribute

history = history or []

meta instance-attribute

meta = meta or {}

request instance-attribute

request = None

captured_xhr instance-attribute

captured_xhr = []

body property

body

Return the raw body of the current Selector without any processing. Useful for binary and non-HTML requests.

Return the raw body of the response as bytes.

generate_css_selector property

generate_css_selector

Generate a CSS selector for the current element

RETURNS DESCRIPTION
str

A string of the generated selector.

generate_full_css_selector property

generate_full_css_selector

Generate a complete CSS selector for the current element

RETURNS DESCRIPTION
str

A string of the generated selector.

generate_xpath_selector property

generate_xpath_selector

Generate an XPath selector for the current element

RETURNS DESCRIPTION
str

A string of the generated selector.

generate_full_xpath_selector property

generate_full_xpath_selector

Generate a complete XPath selector for the current element

RETURNS DESCRIPTION
str

A string of the generated selector.

__slots__ class-attribute instance-attribute

__slots__ = (
    "url",
    "encoding",
    "__adaptive_enabled",
    "_root",
    "_storage",
    "__keep_comments",
    "__huge_tree_enabled",
    "__attributes",
    "__text",
    "__tag",
    "__keep_cdata",
    "_raw_body",
)

url instance-attribute

url = url

encoding instance-attribute

encoding = encoding

tag property

tag

Get the tag name of the element

text property

text

Get text content of the element

attrib property

attrib

Get attributes of the element

html_content property

html_content

Return the inner HTML code of the element

parent property

parent

Return the direct parent of the element or None otherwise

below_elements property

below_elements

Return all elements under the current element in the DOM tree

children property

children

Return the children elements of the current element or empty list otherwise

siblings property

siblings

Return other children of the current element's parent or empty list otherwise

path property

path

Returns a list of type Selectors that contains the path leading to the current element from the root.

next property

next

Returns the next element of the current element in the children of the parent or None otherwise.

previous property

previous

Returns the previous element of the current element in the children of the parent or None otherwise.

extract class-attribute instance-attribute

extract = getall

extract_first class-attribute instance-attribute

extract_first = get

follow

follow(
    url,
    sid="",
    callback=None,
    priority=None,
    dont_filter=False,
    meta=None,
    referer_flow=True,
    **kwargs
)

Create a Request to follow a URL.

This is a helper method for spiders to easily follow links found in pages.

IMPORTANT: The below arguments if left empty, the corresponding value from the previous request will be used. The only exception is dont_filter.

PARAMETER DESCRIPTION
url

The URL to follow (can be relative, will be joined with current URL)

TYPE: str

sid

The session id to use

TYPE: str DEFAULT: ''

callback

Spider callback method to use

TYPE: Callable[[Response], AsyncGenerator[Union[Dict[str, Any], Request, None], None]] | None DEFAULT: None

priority

The priority number to use, the higher the number, the higher priority to be processed first.

TYPE: int | None DEFAULT: None

dont_filter

If this request has been done before, disable the filter to allow it again.

TYPE: bool DEFAULT: False

meta

Additional meta data to included in the request

TYPE: dict[str, Any] | None DEFAULT: None

referer_flow

Enabled by default, set the current response url as referer for the new request url.

TYPE: bool DEFAULT: True

kwargs

Additional Request arguments

TYPE: Any DEFAULT: {}

RETURNS DESCRIPTION
Any

Request object ready to be yielded

Source code in scrapling/engines/toolbelt/custom.py
def follow(
    self,
    url: str,
    sid: str = "",
    callback: Callable[["Response"], AsyncGenerator[Union[Dict[str, Any], "Request", None], None]] | None = None,
    priority: int | None = None,
    dont_filter: bool = False,
    meta: dict[str, Any] | None = None,
    referer_flow: bool = True,
    **kwargs: Any,
) -> Any:
    """Create a Request to follow a URL.

    This is a helper method for spiders to easily follow links found in pages.

    **IMPORTANT**: The below arguments if left empty, the corresponding value from the previous request will be used. The only exception is `dont_filter`.

    :param url: The URL to follow (can be relative, will be joined with current URL)
    :param sid: The session id to use
    :param callback: Spider callback method to use
    :param priority: The priority number to use, the higher the number, the higher priority to be processed first.
    :param dont_filter: If this request has been done before, disable the filter to allow it again.
    :param meta: Additional meta data to included in the request
    :param referer_flow: Enabled by default, set the current response url as referer for the new request url.
    :param kwargs: Additional Request arguments
    :return: Request object ready to be yielded
    """
    from scrapling.spiders import Request

    if not self.request or not isinstance(self.request, Request):
        raise TypeError("This response has no request set yet.")

    # Merge original session kwargs with new kwargs (new takes precedence)
    session_kwargs = {**self.request._session_kwargs, **kwargs}

    if referer_flow:
        # For requests
        headers = session_kwargs.get("headers", {})
        headers["referer"] = self.url
        session_kwargs["headers"] = headers

        # For browsers
        extra_headers = session_kwargs.get("extra_headers", {})
        extra_headers["referer"] = self.url
        session_kwargs["extra_headers"] = extra_headers

        session_kwargs["google_search"] = False

    return Request(
        url=self.urljoin(url),
        sid=sid or self.request.sid,
        callback=callback or self.request.callback,
        priority=priority if priority is not None else self.request.priority,
        dont_filter=dont_filter,
        meta={**(self.meta or {}), **(meta or {})},
        **session_kwargs,
    )

__str__

__str__()
Source code in scrapling/engines/toolbelt/custom.py
def __str__(self) -> str:
    return f"<{self.status} {self.url}>"

__getitem__

__getitem__(key)
Source code in scrapling/parser.py
def __getitem__(self, key: str) -> TextHandler:
    if self._is_text_node(self._root):
        raise TypeError("Text nodes do not have attributes")
    return self.attrib[key]

__contains__

__contains__(key)
Source code in scrapling/parser.py
def __contains__(self, key: str) -> bool:
    if self._is_text_node(self._root):
        return False
    return key in self.attrib

__getstate__

__getstate__()
Source code in scrapling/parser.py
def __getstate__(self) -> Any:
    # lxml don't like it :)
    raise TypeError("Can't pickle Selector objects")

get_all_text

get_all_text(
    separator="\n",
    strip=False,
    ignore_tags=("script", "style"),
    valid_values=True,
)

Get all child strings of this element, concatenated using the given separator.

PARAMETER DESCRIPTION
separator

Strings will be concatenated using this separator.

TYPE: str DEFAULT: '\n'

strip

If True, strings will be stripped before being concatenated.

TYPE: bool DEFAULT: False

ignore_tags

A tuple of all tag names you want to ignore

TYPE: Tuple DEFAULT: ('script', 'style')

valid_values

If enabled, elements with text-content that is empty or only whitespaces will be ignored

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
TextHandler

A TextHandler

Source code in scrapling/parser.py
def get_all_text(
    self,
    separator: str = "\n",
    strip: bool = False,
    ignore_tags: Tuple = (
        "script",
        "style",
    ),
    valid_values: bool = True,
) -> TextHandler:
    """Get all child strings of this element, concatenated using the given separator.

    :param separator: Strings will be concatenated using this separator.
    :param strip: If True, strings will be stripped before being concatenated.
    :param ignore_tags: A tuple of all tag names you want to ignore
    :param valid_values: If enabled, elements with text-content that is empty or only whitespaces will be ignored

    :return: A TextHandler
    """
    if self._is_text_node(self._root):
        return TextHandler(str(self._root))

    ignored_elements: set[Any] = set()
    if ignore_tags:
        ignored_elements.update(self._root.iter(*ignore_tags))

    _all_strings = []

    def append_text(text: str) -> None:
        processed_text = text.strip() if strip else text
        if not valid_values or processed_text.strip():
            _all_strings.append(processed_text)

    def is_visible_text_node(text_node: _ElementUnicodeResult) -> bool:
        parent = text_node.getparent()
        if parent is None:
            return False

        owner = parent.getparent() if text_node.is_tail else parent
        while owner is not None:
            if owner in ignored_elements:
                return False
            owner = owner.getparent()
        return True

    for text_node in cast(list[_ElementUnicodeResult], _find_all_text_nodes(self._root)):
        text = str(text_node)
        if text and is_visible_text_node(text_node):
            append_text(text)

    return cast(TextHandler, TextHandler(separator).join(_all_strings))

urljoin

urljoin(relative_url)

Join this Selector's url with a relative url to form an absolute full URL.

Source code in scrapling/parser.py
def urljoin(self, relative_url: str) -> str:
    """Join this Selector's url with a relative url to form an absolute full URL."""
    return urljoin(self.url, relative_url)

prettify

prettify()

Return a prettified version of the element's inner html-code

Source code in scrapling/parser.py
def prettify(self) -> TextHandler:
    """Return a prettified version of the element's inner html-code"""
    if self._is_text_node(self._root):
        return TextHandler(str(self._root))
    content = tostring(
        self._root,
        encoding=self.encoding,
        pretty_print=True,
        method="html",
        with_tail=False,
    )
    if isinstance(content, bytes):
        content = content.strip().decode(self.encoding)
    return TextHandler(content)

has_class

has_class(class_name)

Check if the element has a specific class

PARAMETER DESCRIPTION
class_name

The class name to check for

TYPE: str

RETURNS DESCRIPTION
bool

True if element has class with that name otherwise False

Source code in scrapling/parser.py
def has_class(self, class_name: str) -> bool:
    """Check if the element has a specific class
    :param class_name: The class name to check for
    :return: True if element has class with that name otherwise False
    """
    if self._is_text_node(self._root):
        return False
    return class_name in self._root.classes

iterancestors

iterancestors()

Return a generator that loops over all ancestors of the element, starting with the element's parent.

Source code in scrapling/parser.py
def iterancestors(self) -> Generator["Selector", None, None]:
    """Return a generator that loops over all ancestors of the element, starting with the element's parent."""
    if self._is_text_node(self._root):
        return
    for ancestor in self._root.iterancestors():
        yield self.__element_convertor(ancestor)

find_ancestor

find_ancestor(func)

Loop over all ancestors of the element till one match the passed function

PARAMETER DESCRIPTION
func

A function that takes each ancestor as an argument and returns True/False

TYPE: Callable[[Selector], bool]

RETURNS DESCRIPTION
Optional[Selector]

The first ancestor that match the function or None otherwise.

Source code in scrapling/parser.py
def find_ancestor(self, func: Callable[["Selector"], bool]) -> Optional["Selector"]:
    """Loop over all ancestors of the element till one match the passed function
    :param func: A function that takes each ancestor as an argument and returns True/False
    :return: The first ancestor that match the function or ``None`` otherwise.
    """
    for ancestor in self.iterancestors():
        if func(ancestor):
            return ancestor
    return None

get

get()

Serialize this element to a string. For text nodes, returns the text value. For HTML elements, returns the outer HTML.

Source code in scrapling/parser.py
def get(self) -> TextHandler:
    """
    Serialize this element to a string.
    For text nodes, returns the text value. For HTML elements, returns the outer HTML.
    """
    if self._is_text_node(self._root):
        return TextHandler(str(self._root))
    return self.html_content

getall

getall()

Return a single-element list containing this element's serialized string.

Source code in scrapling/parser.py
def getall(self) -> TextHandlers:
    """Return a single-element list containing this element's serialized string."""
    return TextHandlers([self.get()])

__repr__

__repr__()
Source code in scrapling/parser.py
def __repr__(self) -> str:
    length_limit = 40

    if self._is_text_node(self._root):
        text = str(self._root)
        if len(text) > length_limit:
            text = text[:length_limit].strip() + "..."
        return f"<text='{text}'>"

    content = clean_spaces(self.html_content)
    if len(content) > length_limit:
        content = content[:length_limit].strip() + "..."
    data = f"<data='{content}'"

    if self.parent:
        parent_content = clean_spaces(self.parent.html_content)
        if len(parent_content) > length_limit:
            parent_content = parent_content[:length_limit].strip() + "..."

        data += f" parent='{parent_content}'"

    return data + ">"

relocate

relocate(element, percentage=40, selector_type=False)

This function will search again for the element in the page tree, used automatically on page structure change

PARAMETER DESCRIPTION
element

The element we want to relocate in the tree

TYPE: Union[Dict, HtmlElement, Selector]

percentage

The minimum percentage to accept and not going lower than that. Be aware that the percentage calculation depends solely on the page structure, so don't play with this number unless you must know what you are doing!

TYPE: int DEFAULT: 40

selector_type

If True, the return result will be converted to Selectors object

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
Union[List[HtmlElement], Selectors]

List of pure HTML elements that got the highest matching score or 'Selectors' object

Source code in scrapling/parser.py
def relocate(
    self,
    element: Union[Dict, HtmlElement, "Selector"],
    percentage: int = 40,
    selector_type: bool = False,
) -> Union[List[HtmlElement], "Selectors"]:
    """This function will search again for the element in the page tree, used automatically on page structure change

    :param element: The element we want to relocate in the tree
    :param percentage: The minimum percentage to accept and not going lower than that. Be aware that the percentage
     calculation depends solely on the page structure, so don't play with this number unless you must know
     what you are doing!
    :param selector_type: If True, the return result will be converted to `Selectors` object
    :return: List of pure HTML elements that got the highest matching score or 'Selectors' object
    """
    score_table: Dict[float, List[Any]] = {}
    # Note: `element` will most likely always be a dictionary at this point.
    if isinstance(element, self.__class__):
        element = element._root

    if issubclass(type(element), HtmlElement):
        element = _StorageTools.element_to_dict(element)

    for node in cast(List, _find_all_elements(self._root)):
        # Collect all elements in the page, then for each element get the matching score of it against the node.
        # Hence: the code doesn't stop even if the score was 100%
        # because there might be another element(s) left in page with the same score
        score = self.__calculate_similarity_score(cast(Dict, element), node)
        score_table.setdefault(score, []).append(node)

    if score_table:
        highest_probability = max(score_table.keys())
        if score_table[highest_probability] and highest_probability >= percentage:
            if log.getEffectiveLevel() < 20:
                # No need to execute this part if the logging level is not debugging
                log.debug(f"Highest probability was {highest_probability}%")
                log.debug("Top 5 best matching elements are: ")
                for percent in tuple(sorted(score_table.keys(), reverse=True))[:5]:
                    log.debug(f"{percent} -> {self.__elements_convertor(score_table[percent])}")

            if not selector_type:
                return score_table[highest_probability]
            return self.__elements_convertor(score_table[highest_probability])
        log.warning(
            f"Adaptive relocation found no element above the {percentage}% threshold "
            f"(top score: {highest_probability}%). Lower `percentage` if this is the right element."
        )
    return []

css

css(
    selector,
    identifier="",
    adaptive=False,
    auto_save=False,
    percentage=40,
)

Search the current tree with CSS3 selectors

Important: It's recommended to use the identifier argument if you plan to use a different selector later and want to relocate the same element(s)

PARAMETER DESCRIPTION
selector

The CSS3 selector to be used.

TYPE: str

adaptive

Enabled will make the function try to relocate the element if it was 'saved' before

TYPE: bool DEFAULT: False

identifier

A string that will be used to save/retrieve element's data in adaptive, otherwise the selector will be used.

TYPE: str DEFAULT: ''

auto_save

Automatically save new elements for adaptive later

TYPE: bool DEFAULT: False

percentage

The minimum percentage to accept while adaptive is working and not going lower than that. Be aware that the percentage calculation depends solely on the page structure, so don't play with this number unless you must know what you are doing!

TYPE: int DEFAULT: 40

RETURNS DESCRIPTION
Selectors

Selectors class.

Source code in scrapling/parser.py
def css(
    self,
    selector: str,
    identifier: str = "",
    adaptive: bool = False,
    auto_save: bool = False,
    percentage: int = 40,
) -> "Selectors":
    """Search the current tree with CSS3 selectors

    **Important:
    It's recommended to use the identifier argument if you plan to use a different selector later
    and want to relocate the same element(s)**

    :param selector: The CSS3 selector to be used.
    :param adaptive: Enabled will make the function try to relocate the element if it was 'saved' before
    :param identifier: A string that will be used to save/retrieve element's data in adaptive,
     otherwise the selector will be used.
    :param auto_save: Automatically save new elements for `adaptive` later
    :param percentage: The minimum percentage to accept while `adaptive` is working and not going lower than that.
     Be aware that the percentage calculation depends solely on the page structure, so don't play with this
     number unless you must know what you are doing!

    :return: `Selectors` class.
    """
    if self._is_text_node(self._root):
        return Selectors()

    try:
        if not self.__adaptive_enabled or "," not in selector:
            # No need to split selectors in this case, let's save some CPU cycles :)
            xpath_selector = _css_to_xpath(selector)
            return self.xpath(
                xpath_selector,
                identifier or selector,
                adaptive,
                auto_save,
                percentage,
            )

        results = Selectors()
        for single_selector in split_selectors(selector):
            # I'm doing this only so the `save` function saves data correctly for combined selectors
            # Like using the ',' to combine two different selectors that point to different elements.
            xpath_selector = _css_to_xpath(single_selector.canonical())
            results += self.xpath(
                xpath_selector,
                identifier or single_selector.canonical(),
                adaptive,
                auto_save,
                percentage,
            )

        return Selectors(results)
    except (
        SelectorError,
        SelectorSyntaxError,
    ) as e:
        raise SelectorSyntaxError(f"Invalid CSS selector '{selector}': {str(e)}") from e

xpath

xpath(
    selector,
    identifier="",
    adaptive=False,
    auto_save=False,
    percentage=40,
    **kwargs
)

Search the current tree with XPath selectors

Important: It's recommended to use the identifier argument if you plan to use a different selector later and want to relocate the same element(s)

Note: Additional keyword arguments will be passed as XPath variables in the XPath expression!

PARAMETER DESCRIPTION
selector

The XPath selector to be used.

TYPE: str

adaptive

Enabled will make the function try to relocate the element if it was 'saved' before

TYPE: bool DEFAULT: False

identifier

A string that will be used to save/retrieve element's data in adaptive, otherwise the selector will be used.

TYPE: str DEFAULT: ''

auto_save

Automatically save new elements for adaptive later

TYPE: bool DEFAULT: False

percentage

The minimum percentage to accept while adaptive is working and not going lower than that. Be aware that the percentage calculation depends solely on the page structure, so don't play with this number unless you must know what you are doing!

TYPE: int DEFAULT: 40

RETURNS DESCRIPTION
Selectors

Selectors class.

Source code in scrapling/parser.py
def xpath(
    self,
    selector: str,
    identifier: str = "",
    adaptive: bool = False,
    auto_save: bool = False,
    percentage: int = 40,
    **kwargs: Any,
) -> "Selectors":
    """Search the current tree with XPath selectors

    **Important:
    It's recommended to use the identifier argument if you plan to use a different selector later
    and want to relocate the same element(s)**

     Note: **Additional keyword arguments will be passed as XPath variables in the XPath expression!**

    :param selector: The XPath selector to be used.
    :param adaptive: Enabled will make the function try to relocate the element if it was 'saved' before
    :param identifier: A string that will be used to save/retrieve element's data in adaptive,
     otherwise the selector will be used.
    :param auto_save: Automatically save new elements for `adaptive` later
    :param percentage: The minimum percentage to accept while `adaptive` is working and not going lower than that.
     Be aware that the percentage calculation depends solely on the page structure, so don't play with this
     number unless you must know what you are doing!

    :return: `Selectors` class.
    """
    if self._is_text_node(self._root):
        return Selectors()

    try:
        if elements := self._root.xpath(selector, **kwargs):
            if not self.__adaptive_enabled and auto_save:
                log.warning(
                    "Argument `auto_save` will be ignored because `adaptive` wasn't enabled on initialization. Check docs for more info."
                )
            elif self.__adaptive_enabled and auto_save:
                self.save(elements[0], identifier or selector)

            return self.__handle_elements(elements)
        elif self.__adaptive_enabled:
            if adaptive:
                element_data = self.retrieve(identifier or selector)
                if element_data:
                    elements = self.relocate(element_data, percentage)
                    if elements and auto_save:
                        self.save(elements[0], identifier or selector)

            return self.__handle_elements(elements)
        else:
            if adaptive:
                log.warning(
                    "Argument `adaptive` will be ignored because `adaptive` wasn't enabled on initialization. Check docs for more info."
                )
            elif auto_save:
                log.warning(
                    "Argument `auto_save` will be ignored because `adaptive` wasn't enabled on initialization. Check docs for more info."
                )

            return self.__handle_elements(elements)

    except (
        SelectorError,
        SelectorSyntaxError,
        XPathError,
        XPathEvalError,
    ) as e:
        raise SelectorSyntaxError(f"Invalid XPath selector: {selector}") from e

find_all

find_all(*args, **kwargs)

Find elements by filters of your creations for ease.

PARAMETER DESCRIPTION
args

Tag name(s), iterable of tag names, regex patterns, function, or a dictionary of elements' attributes. Leave empty for selecting all.

TYPE: str | Iterable[str] | Pattern | Callable | Dict[str, str] DEFAULT: ()

kwargs

The attributes you want to filter elements based on it.

TYPE: str DEFAULT: {}

RETURNS DESCRIPTION
Selectors

The Selectors object of the elements or empty list

Source code in scrapling/parser.py
def find_all(
    self,
    *args: str | Iterable[str] | Pattern | Callable | Dict[str, str],
    **kwargs: str,
) -> "Selectors":
    """Find elements by filters of your creations for ease.

    :param args: Tag name(s), iterable of tag names, regex patterns, function, or a dictionary of elements' attributes. Leave empty for selecting all.
    :param kwargs: The attributes you want to filter elements based on it.
    :return: The `Selectors` object of the elements or empty list
    """
    if self._is_text_node(self._root):
        return Selectors()

    if not args and not kwargs:
        raise TypeError("You have to pass something to search with, like tag name(s), tag attributes, or both.")

    attributes: Dict[str, Any] = dict()
    tags: Set[str] = set()
    patterns: Set[Pattern] = set()
    results, functions, selectors = Selectors(), [], []

    # Brace yourself for a wonderful journey!
    for arg in args:
        if isinstance(arg, str):
            tags.add(arg)

        elif type(arg) in (list, tuple, set):
            arg = cast(Iterable, arg)  # Type narrowing for type checkers like pyright
            if not all(map(lambda x: isinstance(x, str), arg)):
                raise TypeError("Nested Iterables are not accepted, only iterables of tag names are accepted")
            tags.update(set(arg))

        elif isinstance(arg, dict):
            if not all([(isinstance(k, str) and isinstance(v, str)) for k, v in arg.items()]):
                raise TypeError(
                    "Nested dictionaries are not accepted, only string keys and string values are accepted"
                )
            attributes.update(arg)

        elif isinstance(arg, re_Pattern):
            patterns.add(arg)

        elif callable(arg):
            if len(signature(arg).parameters) > 0:
                functions.append(arg)
            else:
                raise TypeError(
                    "Callable filter function must have at least one argument to take `Selector` objects."
                )

        else:
            raise TypeError(f'Argument with type "{type(arg)}" is not accepted, please read the docs.')

    if not all([(isinstance(k, str) and isinstance(v, str)) for k, v in kwargs.items()]):
        raise TypeError("Only string values are accepted for arguments")

    for attribute_name, value in kwargs.items():
        # Only replace names for kwargs, replacing them in dictionaries doesn't make sense
        attribute_name = _whitelisted.get(attribute_name, attribute_name)
        attributes[attribute_name] = value

    # It's easier and faster to build a selector than traversing the tree
    tags = tags or set("*")
    for tag in tags:
        selector = tag
        for key, value in attributes.items():
            value = value.replace('"', r"\"")  # Escape double quotes in user input
            # Not escaping anything with the key so the user can pass patterns like {'href*': '/p/'} or get errors :)
            selector += '[{}="{}"]'.format(key, value)
        if selector != "*":
            selectors.append(selector)

    if selectors:
        results = cast(Selectors, self.css(", ".join(selectors)))
        if results:
            # From the results, get the ones that fulfill passed regex patterns
            for pattern in patterns:
                results = results.filter(lambda e: e.text.re(pattern, check_match=True))

            # From the results, get the ones that fulfill passed functions
            for function in functions:
                results = results.filter(function)
    else:
        results = results or self.below_elements
        for pattern in patterns:
            results = results.filter(lambda e: e.text.re(pattern, check_match=True))

        # Collect an element if it fulfills the passed function otherwise
        for function in functions:
            results = results.filter(function)

    return results

find

find(*args, **kwargs)

Find elements by filters of your creations for ease, then return the first result. Otherwise return None.

PARAMETER DESCRIPTION
args

Tag name(s), iterable of tag names, regex patterns, function, or a dictionary of elements' attributes. Leave empty for selecting all.

TYPE: str | Iterable[str] | Pattern | Callable | Dict[str, str] DEFAULT: ()

kwargs

The attributes you want to filter elements based on it.

TYPE: str DEFAULT: {}

RETURNS DESCRIPTION
Optional[Selector]

The Selector object of the element or None if the result didn't match

Source code in scrapling/parser.py
def find(
    self,
    *args: str | Iterable[str] | Pattern | Callable | Dict[str, str],
    **kwargs: str,
) -> Optional["Selector"]:
    """Find elements by filters of your creations for ease, then return the first result. Otherwise return `None`.

    :param args: Tag name(s), iterable of tag names, regex patterns, function, or a dictionary of elements' attributes. Leave empty for selecting all.
    :param kwargs: The attributes you want to filter elements based on it.
    :return: The `Selector` object of the element or `None` if the result didn't match
    """
    for element in self.find_all(*args, **kwargs):
        return element
    return None

save

save(element, identifier)

Saves the element's unique properties to the storage for retrieval and relocation later

PARAMETER DESCRIPTION
element

The element itself that we want to save to storage, it can be a Selector or pure HtmlElement

TYPE: HtmlElement

identifier

This is the identifier that will be used to retrieve the element later from the storage. See the docs for more info.

TYPE: str

Source code in scrapling/parser.py
def save(self, element: HtmlElement, identifier: str) -> None:
    """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 a ` Selector ` or pure ` HtmlElement `
    :param identifier: This is the identifier that will be used to retrieve the element later from the storage. See
        the docs for more info.
    """
    if self.__adaptive_enabled and self._storage:
        target_element: Any = element
        if isinstance(target_element, self.__class__):
            target_element = target_element._root

        if self._is_text_node(target_element):
            target_element = target_element.getparent()

        self._storage.save(target_element, identifier)
    else:
        raise RuntimeError(
            "Can't use `adaptive` features while it's disabled globally, you have to start a new class instance."
        )

retrieve

retrieve(identifier)

Using the identifier, we search the storage and return the unique properties of the element

PARAMETER DESCRIPTION
identifier

This is the identifier that will be used to retrieve the element from the storage. See the docs for more info.

TYPE: str

RETURNS DESCRIPTION
Optional[Dict[str, Any]]

A dictionary of the unique properties

Source code in scrapling/parser.py
def retrieve(self, identifier: str) -> Optional[Dict[str, Any]]:
    """Using the identifier, we search the storage and return the unique properties of the element

    :param identifier: This is the identifier that will be used to retrieve the element from the storage. See
        the docs for more info.
    :return: A dictionary of the unique properties
    """
    if self.__adaptive_enabled and self._storage:
        return self._storage.retrieve(identifier)

    raise RuntimeError(
        "Can't use `adaptive` features while it's disabled globally, you have to start a new class instance."
    )

json

json()

Return JSON response if the response is jsonable otherwise throws error

Source code in scrapling/parser.py
def json(self) -> Dict:
    """Return JSON response if the response is jsonable otherwise throws error"""
    if self._is_text_node(self._root):
        return TextHandler(str(self._root)).json()
    if self._raw_body and isinstance(self._raw_body, (str, bytes)):
        if isinstance(self._raw_body, str):
            return TextHandler(self._raw_body).json()
        else:
            if TYPE_CHECKING:
                assert isinstance(self._raw_body, bytes)
            return TextHandler(self._raw_body.decode()).json()
    elif self.text:
        return self.text.json()
    else:
        return self.get_all_text(strip=True).json()

re

re(
    regex,
    replace_entities=True,
    clean_match=False,
    case_sensitive=True,
)

Apply the given regex to the current text and return a list of strings with the matches.

PARAMETER DESCRIPTION
regex

Can be either a compiled regular expression or a string.

TYPE: str | Pattern[str]

replace_entities

If enabled character entity references are replaced by their corresponding character

TYPE: bool DEFAULT: True

clean_match

if enabled, this will ignore all whitespaces and consecutive spaces while matching

TYPE: bool DEFAULT: False

case_sensitive

if disabled, the function will set the regex to ignore the letters case while compiling it

TYPE: bool DEFAULT: True

Source code in scrapling/parser.py
def re(
    self,
    regex: str | Pattern[str],
    replace_entities: bool = True,
    clean_match: bool = False,
    case_sensitive: bool = True,
) -> TextHandlers:
    """Apply the given regex to the current text and return a list of strings with the matches.

    :param regex: Can be either a compiled regular expression or a string.
    :param replace_entities: If enabled character entity references are replaced by their corresponding character
    :param clean_match: if enabled, this will ignore all whitespaces and consecutive spaces while matching
    :param case_sensitive: if disabled, the function will set the regex to ignore the letters case while compiling it
    """
    return self.text.re(regex, replace_entities, clean_match, case_sensitive)

re_first

re_first(
    regex,
    default=None,
    replace_entities=True,
    clean_match=False,
    case_sensitive=True,
)

Apply the given regex to text and return the first match if found, otherwise return the default value.

PARAMETER DESCRIPTION
regex

Can be either a compiled regular expression or a string.

TYPE: str | Pattern[str]

default

The default value to be returned if there is no match

DEFAULT: None

replace_entities

if enabled character entity references are replaced by their corresponding character

TYPE: bool DEFAULT: True

clean_match

if enabled, this will ignore all whitespaces and consecutive spaces while matching

TYPE: bool DEFAULT: False

case_sensitive

if disabled, the function will set the regex to ignore the letters case while compiling it

TYPE: bool DEFAULT: True

Source code in scrapling/parser.py
def re_first(
    self,
    regex: str | Pattern[str],
    default=None,
    replace_entities: bool = True,
    clean_match: bool = False,
    case_sensitive: bool = True,
) -> TextHandler:
    """Apply the given regex to text and return the first match if found, otherwise return the default value.

    :param regex: Can be either a compiled regular expression or a string.
    :param default: The default value to be returned if there is no match
    :param replace_entities: if enabled character entity references are replaced by their corresponding character
    :param clean_match: if enabled, this will ignore all whitespaces and consecutive spaces while matching
    :param case_sensitive: if disabled, the function will set the regex to ignore the letters case while compiling it
    """
    return self.text.re_first(regex, default, replace_entities, clean_match, case_sensitive)

find_similar

find_similar(
    similarity_threshold=0.2,
    ignore_attributes=("href", "src"),
    match_text=False,
)

Find elements that are in the same tree depth in the page with the same tag name and same parent tag etc... then return the ones that match the current element attributes with a percentage higher than the input threshold.

This function is inspired by AutoScraper and made for cases where you, for example, found a product div inside a products-list container and want to find other products using that element as a starting point EXCEPT this function works in any case without depending on the element type.

PARAMETER DESCRIPTION
similarity_threshold

The percentage to use while comparing element attributes. Note: Elements found before attributes matching/comparison will be sharing the same depth, same tag name, same parent tag name, and same grand parent tag name. So they are 99% likely to be correct unless you are extremely unlucky, then attributes matching comes into play, so don't play with this number unless you are getting the results you don't want. Also, if the current element doesn't have attributes and the similar element as well, then it's a 100% match.

TYPE: float DEFAULT: 0.2

ignore_attributes

Attribute names passed will be ignored while matching the attributes in the last step. The default value is to ignore href and src as URLs can change a lot between elements, so it's unreliable

TYPE: List | Tuple DEFAULT: ('href', 'src')

match_text

If True, element text content will be taken into calculation while matching. Not recommended to use in normal cases, but it depends.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
Selectors

A Selectors container of Selector objects or empty list

Source code in scrapling/parser.py
def find_similar(
    self,
    similarity_threshold: float = 0.2,
    ignore_attributes: List | Tuple = (
        "href",
        "src",
    ),
    match_text: bool = False,
) -> "Selectors":
    """Find elements that are in the same tree depth in the page with the same tag name and same parent tag etc...
    then return the ones that match the current element attributes with a percentage higher than the input threshold.

    This function is inspired by AutoScraper and made for cases where you, for example, found a product div inside
    a products-list container and want to find other products using that element as a starting point EXCEPT
    this function works in any case without depending on the element type.

    :param similarity_threshold: The percentage to use while comparing element attributes.
        Note: Elements found before attributes matching/comparison will be sharing the same depth, same tag name,
        same parent tag name, and same grand parent tag name. So they are 99% likely to be correct unless you are
        extremely unlucky, then attributes matching comes into play, so don't play with this number unless
        you are getting the results you don't want.
        Also, if the current element doesn't have attributes and the similar element as well, then it's a 100% match.
    :param ignore_attributes: Attribute names passed will be ignored while matching the attributes in the last step.
        The default value is to ignore `href` and `src` as URLs can change a lot between elements, so it's unreliable
    :param match_text: If True, element text content will be taken into calculation while matching.
        Not recommended to use in normal cases, but it depends.

    :return: A ``Selectors`` container of ``Selector`` objects or empty list
    """
    if self._is_text_node(self._root):
        return Selectors()

    # We will use the elements' root from now on to get the speed boost of using Lxml directly
    root = self._root
    similar_elements = list()

    current_depth = len(list(root.iterancestors()))
    target_attrs = self.__get_attributes(root, ignore_attributes) if ignore_attributes else root.attrib

    path_parts = [self.tag]
    if (parent := root.getparent()) is not None:
        path_parts.insert(0, parent.tag)
        if (grandparent := parent.getparent()) is not None:
            path_parts.insert(0, grandparent.tag)

    xpath_path = "//{}".format("/".join(path_parts))
    potential_matches = root.xpath(f"{xpath_path}[count(ancestor::*) = {current_depth}]")

    for potential_match in potential_matches:
        if potential_match != root and self.__are_alike(
            root,
            target_attrs,
            potential_match,
            ignore_attributes,
            similarity_threshold,
            match_text,
        ):
            similar_elements.append(potential_match)

    return Selectors(map(self.__element_convertor, similar_elements))

find_by_text

find_by_text(
    text,
    first_match=True,
    partial=False,
    case_sensitive=False,
    clean_match=True,
)

Find elements that its text content fully/partially matches input.

PARAMETER DESCRIPTION
text

Text query to match

TYPE: str

first_match

Returns the first element that matches conditions, enabled by default

TYPE: bool DEFAULT: True

partial

If enabled, the function returns elements that contain the input text

TYPE: bool DEFAULT: False

case_sensitive

if enabled, the letters case will be taken into consideration

TYPE: bool DEFAULT: False

clean_match

if enabled, this will ignore all whitespaces and consecutive spaces while matching

TYPE: bool DEFAULT: True

Source code in scrapling/parser.py
def find_by_text(
    self,
    text: str,
    first_match: bool = True,
    partial: bool = False,
    case_sensitive: bool = False,
    clean_match: bool = True,
) -> Union["Selectors", "Selector"]:
    """Find elements that its text content fully/partially matches input.
    :param text: Text query to match
    :param first_match: Returns the first element that matches conditions, enabled by default
    :param partial: If enabled, the function returns elements that contain the input text
    :param case_sensitive: if enabled, the letters case will be taken into consideration
    :param clean_match: if enabled, this will ignore all whitespaces and consecutive spaces while matching
    """
    if self._is_text_node(self._root):
        return Selectors()

    results = Selectors()
    if not case_sensitive:
        text = text.lower()

    possible_targets = cast(List, _find_all_elements_with_spaces(self._root))
    if possible_targets:
        for node in self.__elements_convertor(possible_targets):
            """Check if element matches given text otherwise, traverse the children tree and iterate"""
            node_text: TextHandler = node.text
            if clean_match:
                node_text = TextHandler(node_text.clean())

            if not case_sensitive:
                node_text = TextHandler(node_text.lower())

            if partial:
                if text in node_text:
                    results.append(node)
            elif text == node_text:
                results.append(node)

            if first_match and results:
                # we got an element so we should stop
                break

        if first_match:
            if results:
                return results[0]
    return results

find_by_regex

find_by_regex(
    query,
    first_match=True,
    case_sensitive=False,
    clean_match=True,
)

Find elements that its text content matches the input regex pattern.

PARAMETER DESCRIPTION
query

Regex query/pattern to match

TYPE: str | Pattern[str]

first_match

Return the first element that matches conditions; enabled by default.

TYPE: bool DEFAULT: True

case_sensitive

If enabled, the letters case will be taken into consideration in the regex.

TYPE: bool DEFAULT: False

clean_match

If enabled, this will ignore all whitespaces and consecutive spaces while matching.

TYPE: bool DEFAULT: True

Source code in scrapling/parser.py
def find_by_regex(
    self,
    query: str | Pattern[str],
    first_match: bool = True,
    case_sensitive: bool = False,
    clean_match: bool = True,
) -> Union["Selectors", "Selector"]:
    """Find elements that its text content matches the input regex pattern.
    :param query: Regex query/pattern to match
    :param first_match: Return the first element that matches conditions; enabled by default.
    :param case_sensitive: If enabled, the letters case will be taken into consideration in the regex.
    :param clean_match: If enabled, this will ignore all whitespaces and consecutive spaces while matching.
    """
    if self._is_text_node(self._root):
        return Selectors()

    results = Selectors()

    possible_targets = cast(List, _find_all_elements_with_spaces(self._root))
    if possible_targets:
        for node in self.__elements_convertor(possible_targets):
            """Check if element matches given regex otherwise, traverse the children tree and iterate"""
            node_text = node.text
            if node_text.re(
                query,
                check_match=True,
                clean_match=clean_match,
                case_sensitive=case_sensitive,
            ):
                results.append(node)

            if first_match and results:
                # we got an element so we should stop
                break

        if results and first_match:
            return results[0]
    return results