跳转至

Response 类

Response 类表示所有 Fetcher 返回的 HTTP 响应。它提供状态、响应头、内容和 Cookie,也通过继承 Selector 提供解析功能。

导入 Response 类:

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,
    *,
    _log=True,
    **selector_config
)

基类: 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"
            

所有引擎都返回此类,用于统一不同库的响应类型。

参数 说明
status

HTTP 状态码。

类型: int

reason

HTTP 状态消息。

类型: str

cookies

响应中的 Cookie。

类型: Tuple[Dict[str, str], ...] | Dict[str, str]

headers

响应头。

类型: Dict

request_headers

随请求发送的请求头。

类型: Dict

history

重定向响应列表,如果有的话。

类型: List | None 默认值: None

meta

元数据字典,例如使用的代理。

类型: Dict[str, Any] | None 默认值: None

request

关联的 Spider Request 对象,由 Spider 框架中的爬虫设置。

封装输入 HTML 数据的主要类。可以用 CSS、XPath 表达式或简单文本查找元素。更多信息请查看文档。

此类扩展了 lxml.html.HtmlElement 的功能,同时提供更简单的接口。它不继承 lxml.html.HtmlElement,因为该类型无法通过 pickle 序列化,会使许多依赖引用的操作无法完成。尝试这样做会出现 AssertionError: invalid Element proxy at...。这是 lxml 的一个老问题,详见 this entry <https://bugs.launchpad.net/lxml/+bug/736708>。

参数 说明
content

字符串或 bytes 形式的 HTML 内容。

类型: str | bytes

url

允许将 URL 与 HTML 数据一起保存,供以后读取。

类型: str

encoding

解析 HTML 时使用的编码,默认为 UTF-8。

类型: str 默认值: 'utf-8'

huge_tree

默认启用,解析大型 HTML 文档时应始终启用。它控制 libxml2 的一项功能,该功能会禁止解析某些大型文档,以避免内存耗尽。

root

内部用于传入 etree 对象,代替文本或响应体参数,优先级最高。只有了解其影响时才应使用。

keep_comments

解析 HTML 时是否保留注释,默认关闭。

adaptive

在所有方法中全局关闭自适应功能。此参数的优先级高于类中所有与自适应有关的参数和方法。

storage

自适应功能使用的存储类。更多信息请查看 Docs。

storage_args

传给存储类的 argument->value 键值对字典。为空时使用默认值。

源代码位于 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,
    *,
    _log: bool = True,
    **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.method = method
    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,
    )
    if _log:
        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

status instance-attribute

status = status

method instance-attribute

method = method

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

body property

body

返回当前 Selector 未经处理的原始响应体,适用于二进制和非 HTML 内容。

以 bytes 形式返回响应的原始响应体。

generate_css_selector property

generate_css_selector

为当前元素生成 CSS 选择器。

返回 说明
str

包含所生成选择器的字符串。

generate_full_css_selector property

generate_full_css_selector

为当前元素生成完整的 CSS 选择器。

返回 说明
str

包含所生成选择器的字符串。

generate_xpath_selector property

generate_xpath_selector

为当前元素生成 XPath 选择器。

返回 说明
str

包含所生成选择器的字符串。

generate_full_xpath_selector property

generate_full_xpath_selector

为当前元素生成完整的 XPath 选择器。

返回 说明
str

包含所生成选择器的字符串。

__slots__ class-attribute instance-attribute

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

url instance-attribute

url = url

encoding instance-attribute

encoding = encoding

tag property

tag

获取元素的标签名。

text property

text

获取元素的文本内容。

attrib property

attrib

获取元素的属性。

html_content property

html_content

返回元素内部的 HTML 代码。

parent property

parent

返回元素的直接父元素,如果没有则返回 None。

below_elements property

below_elements

返回 DOM 树中当前元素下方的所有元素。

children property

children

返回当前元素的子元素,如果没有则返回空列表。

siblings property

siblings

返回当前元素的父元素下的其他子元素,如果没有则返回空列表。

path property

path

返回 Selectors 类型的列表,包含从根节点到当前元素的路径。

next property

next

返回同一父元素下,当前元素之后的下一个元素,如果没有则返回 None。

previous property

previous

返回同一父元素下,当前元素之前的上一个元素,如果没有则返回 None。

extract class-attribute instance-attribute

extract = getall

extract_first class-attribute instance-attribute

extract_first = get

markdown

markdown(css_selector=None, main_content_only=False)

将响应内容转换为干净的 Markdown。

转换前会始终移除脚本、样式、隐藏内容和提示词注入内容,与 MCP 服务器使用的清理方式相同。需要安装 "markdownify" 包(pip install "scrapling[rag]")。

参数 说明
css_selector

用于限定转换范围的 CSS 选择器。只转换匹配的元素,并合并所有结果。

类型: Optional[str] 默认值: None

main_content_only

只转换 <body> 标签内的内容。

类型: bool 默认值: False

源代码位于 scrapling/engines/toolbelt/custom.py
def markdown(self, css_selector: Optional[str] = None, main_content_only: bool = False) -> str:
    """Convert the response content to clean Markdown.

    Scripts, styles, and hidden/prompt-injection content are always removed before conversion, which is
    the same cleaning the MCP server does. Requires the "markdownify" package (`pip install "scrapling[rag]"`).

    :param css_selector: CSS selector to convert only the matching elements. All matches are concatenated.
    :param main_content_only: Convert only the content inside the `<body>` tag.
    """
    from scrapling.core.shell import Convertor

    page = (cast(Selector, self.css("body").first) or self) if main_content_only else self
    page = Convertor._sanitize_for_ai(Convertor._strip_noise_tags(page))
    pages = [page] if not css_selector else page.css(css_selector)
    return "\n\n".join(Convertor._convert_to_markdown(element.html_content) for element in pages)

follow

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

创建一个 Request 来跟随 URL。

这是一个辅助方法,方便 Spider 跟随页面中的链接。

重要:下列参数如果留空,会使用前一个请求的对应值。唯一例外是 dont_filter。

前一个请求的会话参数也会继承,但不包括 method、params、data、json、files 和 multipart。因此,除非再次传入这些参数,否则跟随链接时会发送普通 GET 请求。

参数 说明
url

要跟随的 URL。可以是相对 URL,会与当前 URL 合并。

类型: str

sid

要使用的会话 ID。

类型: str 默认值: ''

callback

要使用的 Spider 回调方法。

类型: 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

默认启用,将当前响应的 URL 设为新请求的 Referer。

类型: bool 默认值: True

kwargs

额外的 Request 参数。

类型: Any 默认值: {}

返回 说明
Any

可以通过 yield 产出的 Request 对象。

源代码位于 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`.

    The session kwargs of the previous request are inherited as well, except `method`, `params`, `data`, `json`, `files`, and `multipart`, so a followed link is a plain GET unless you pass them again.

    :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)
    inherited = {k: v for k, v in self.request._session_kwargs.items() if k not in _NON_INHERITABLE_KWARGS}
    for key in ("headers", "extra_headers"):
        if inherited.get(key):
            inherited[key] = {k: v for k, v in inherited[key].items() if k.lower() != "content-length"}
    session_kwargs = {**inherited, **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__()
源代码位于 scrapling/engines/toolbelt/custom.py
def __str__(self) -> str:
    return f"<{self.status} {self.url}>"

__getitem__

__getitem__(key)
源代码位于 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)
源代码位于 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__()
源代码位于 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,
)

获取此元素下的所有字符串,并用指定分隔符连接。

参数 说明
separator

连接字符串时使用的分隔符。

类型: str 默认值: '\n'

strip

为 True 时,先去除各字符串两端的空白,再连接。

类型: bool 默认值: False

ignore_tags

包含所有要忽略的标签名的元组。

类型: Tuple 默认值: ('script', 'style')

valid_values

启用后,忽略文本为空或只包含空白的元素。

类型: bool 默认值: True

返回 说明
TextHandler

一个 TextHandler。

源代码位于 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:
        for element in self._root.iter(*ignore_tags):
            if element not in ignored_elements:
                ignored_elements.update(element.iter())

    _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
        return owner not in ignored_elements

    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)

将此 Selector 的 URL 与相对 URL 合并,生成完整的绝对 URL。

源代码位于 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()

返回格式化后更易读的元素内部 HTML 代码。

源代码位于 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)

检查元素是否包含指定的 class。

参数 说明
class_name

要检查的 class 名称。

类型: str

返回 说明
bool

元素包含该名称的 class 时返回 True,否则返回 False。

源代码位于 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()

返回一个生成器,从父元素开始遍历当前元素的所有祖先元素。

源代码位于 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)

遍历元素的所有祖先,直到某个祖先满足传入函数的条件。

参数 说明
func

接收每个祖先元素作为参数,并返回 True 或 False 的函数。

类型: Callable[[Selector], bool]

返回 说明
Optional[Selector]

第一个满足函数条件的祖先元素,如果没有则返回 None。

源代码位于 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()

将此元素序列化为字符串。文本节点返回文本值,HTML 元素返回外层 HTML。

源代码位于 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()

返回仅含一个元素的列表,其中是当前元素序列化后的字符串。

源代码位于 scrapling/parser.py
def getall(self) -> TextHandlers:
    """Return a single-element list containing this element's serialized string."""
    return TextHandlers([self.get()])

__repr__

__repr__()
源代码位于 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)

在页面树中重新查找元素,页面结构变化时会自动使用。

参数 说明
element

要在树中重新定位的元素。

类型: Union[Dict, HtmlElement, Selector]

percentage

可接受的最低匹配百分比。百分比的计算只依赖页面结构,请仅在了解其影响时修改此值。

类型: int 默认值: 40

selector_type

为 True 时,将返回结果转换为 Selectors 对象。

类型: bool 默认值: False

返回 说明
Union[List[HtmlElement], Selectors]

匹配分数最高的原始 HTML 元素列表,或 'Selectors' 对象。

源代码位于 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,
)

使用 CSS3 选择器搜索当前树。

重要:如果以后打算使用其他选择器,并希望重新定位相同元素,建议使用 identifier 参数。

参数 说明
selector

要使用的 CSS3 选择器。

类型: str

adaptive

启用后,如果元素之前已保存,函数会尝试重新定位它。

类型: bool 默认值: False

identifier

用于保存和读取自适应元素数据的字符串。未指定时使用选择器。

类型: str 默认值: ''

auto_save

自动保存新元素,供之后的 adaptive 功能使用。

类型: bool 默认值: False

percentage

adaptive 工作时可接受的最低匹配百分比。百分比的计算只依赖页面结构,请仅在了解其影响时修改此值。

类型: int 默认值: 40

返回 说明
Selectors

Selectors 类。

源代码位于 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
)

使用 XPath 选择器搜索当前树。

重要:如果以后打算使用其他选择器,并希望重新定位相同元素,建议使用 identifier 参数。

注意:额外的关键字参数会作为 XPath 变量传入 XPath 表达式!

参数 说明
selector

要使用的 XPath 选择器。

类型: str

adaptive

启用后,如果元素之前已保存,函数会尝试重新定位它。

类型: bool 默认值: False

identifier

用于保存和读取自适应元素数据的字符串。未指定时使用选择器。

类型: str 默认值: ''

auto_save

自动保存新元素,供之后的 adaptive 功能使用。

类型: bool 默认值: False

percentage

adaptive 工作时可接受的最低匹配百分比。百分比的计算只依赖页面结构,请仅在了解其影响时修改此值。

类型: int 默认值: 40

返回 说明
Selectors

Selectors 类。

源代码位于 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:
        elements = self._root.xpath(selector, **kwargs)
        # string(), count(), boolean() and the like return one value instead of a list of nodes
        if not isinstance(elements, list):
            value = ("1" if elements else "0") if isinstance(elements, bool) else str(elements)
            return self.__elements_convertor([_ElementUnicodeResult(value)])

        if elements:
            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)

通过自己定义的过滤条件查找元素。

参数 说明
args

标签名、多个标签名、标签名的可迭代对象、正则表达式、函数,或元素属性字典。留空表示选择全部。

类型: str | Iterable[str] | Pattern | Callable | Dict[str, str] 默认值: ()

kwargs

用于过滤元素的属性。

类型: str 默认值: {}

返回 说明
Selectors

包含元素的 Selectors 对象,或空列表。

源代码位于 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():
            # Not escaping anything with the key so the user can pass patterns like {'href*': '/p/'} or get errors :)
            if key == "class" and (class_names := value.split()):
                # `class` is a space-separated list, so exact-match [class="x"] misses class="x y"; match each name with ~=
                # An empty/blank value has no names to match, so it falls through to the exact match below
                selector += "".join('[class~="{}"]'.format(_escape_css_string(name)) for name in class_names)
            else:
                selector += '[{}="{}"]'.format(key, _escape_css_string(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)

通过自己定义的过滤条件查找元素,并返回第一个结果。如果没有则返回 None。

参数 说明
args

标签名、多个标签名、标签名的可迭代对象、正则表达式、函数,或元素属性字典。留空表示选择全部。

类型: str | Iterable[str] | Pattern | Callable | Dict[str, str] 默认值: ()

kwargs

用于过滤元素的属性。

类型: str 默认值: {}

返回 说明
Optional[Selector]

元素的 Selector 对象;如果没有匹配结果,则返回 None。

源代码位于 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)

将元素的独特属性保存到存储中,供之后读取和重新定位。

参数 说明
element

要保存的元素本身,可以是 Selector 或原始 HtmlElement。

类型: HtmlElement

identifier

以后从存储中读取元素时使用的标识。更多信息请查看文档。

类型: str

源代码位于 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)

根据标识搜索存储,并返回元素的独特属性。

参数 说明
identifier

从存储中读取元素时使用的标识。更多信息请查看文档。

类型: str

返回 说明
Optional[Dict[str, Any]]

包含独特属性的字典。

源代码位于 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()

如果响应可以解析为 JSON,则返回解析结果,否则抛出错误。

源代码位于 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,
)

对当前文本应用给定的正则表达式,并返回包含匹配字符串的列表。

参数 说明
regex

可以是编译后的正则表达式,也可以是字符串。

类型: str | Pattern[str]

replace_entities

启用后,将字符实体引用替换为对应字符。

类型: bool 默认值: True

clean_match

启用后,匹配时会忽略所有空白及连续空格。

类型: bool 默认值: False

case_sensitive

关闭后,编译正则表达式时会忽略大小写。

类型: bool 默认值: True

源代码位于 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,
)

对文本应用给定的正则表达式,并返回第一个匹配结果。如果没有,则返回默认值。

参数 说明
regex

可以是编译后的正则表达式,也可以是字符串。

类型: str | Pattern[str]

default

没有匹配结果时返回的默认值。

默认值: None

replace_entities

启用后,将字符实体引用替换为对应字符。

类型: bool 默认值: True

clean_match

启用后,匹配时会忽略所有空白及连续空格。

类型: bool 默认值: False

case_sensitive

关闭后,编译正则表达式时会忽略大小写。

类型: bool 默认值: True

源代码位于 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,
)

查找页面树中深度相同、标签名相同、父元素标签名相同等条件一致的元素,然后返回与当前元素的属性匹配度高于指定阈值的元素。

此函数受 AutoScraper 启发。例如,在商品列表容器中找到一个商品 div 后,可以以它为起点查找其他商品。不过,此函数不限于这种场景,也不依赖元素类型。

参数 说明
similarity_threshold

比较元素属性时使用的百分比。注意:在比较属性前,找到的元素已经具有相同深度、相同标签名,以及相同的父元素和祖父元素标签名。因此,除非特别不巧,它们有 99% 的可能已经正确。之后才会比较属性,所以只有结果不符合预期时才应调整此值。如果当前元素和相似元素都没有属性,则视为 100% 匹配。

类型: float 默认值: 0.2

ignore_attributes

在最后一步比较属性时,忽略指定的属性名。默认忽略 href 和 src,因为不同元素的 URL 可能相差很大,不适合用于判断。

类型: List | Tuple 默认值: ('href', 'src')

match_text

为 True 时,将元素文本内容也纳入匹配计算。通常不建议启用,但可以根据情况选择。

类型: bool 默认值: False

返回 说明
Selectors

一个 Selectors 容器,包含 Selector 对象,或为空列表。

源代码位于 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,
)

查找文本内容与输入完全或部分匹配的元素。

参数 说明
text

要匹配的查询文本。

类型: str

first_match

返回第一个符合条件的元素,默认启用。

类型: bool 默认值: True

partial

启用后,返回包含输入文本的元素。

类型: bool 默认值: False

case_sensitive

启用后区分大小写。

类型: bool 默认值: False

clean_match

启用后,匹配时会忽略所有空白及连续空格。

类型: bool 默认值: True

源代码位于 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 map(self.__element_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,
)

查找文本匹配指定正则表达式的元素。

参数 说明
query

用于匹配的正则表达式。

类型: str | Pattern[str]

first_match

只返回第一个满足条件的元素,默认开启。

类型: bool 默认值: True

case_sensitive

启用后,正则表达式区分大小写。

类型: bool 默认值: False

clean_match

启用后,匹配时忽略所有空白字符和连续空格。

类型: bool 默认值: True

源代码位于 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 map(self.__element_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