D4Vinci/Scrapling · error · ExpressionError

The pseudo-element ::{pseudo_element} is unknown

Error message

The pseudo-element ::{pseudo_element} is unknown

What it means

Raised by the non-functional branch of `xpath_pseudo_element` in scrapling/core/translator.py:105. Simple pseudo-elements (no parentheses) are dispatched to `xpath_<name>_simple_pseudo_element`; Scrapling only registers `::text` (plus cssselect's built-ins where applicable). Using any other simple pseudo-element such as `::before`, `::after`, `::first-line`, `::placeholder`, or `::marker` raises `ExpressionError` because pseudo-elements that exist only in browser rendering have no XPath representation.

Source

Thrown at scrapling/core/translator.py:105

        # https://github.com/python/mypy/issues/14757
        xpath = super().xpath_element(selector)  # type: ignore[safe-super]
        return XPathExpr.from_xpath(xpath)

    def xpath_pseudo_element(self, xpath: OriginalXPathExpr, pseudo_element: PseudoElement) -> OriginalXPathExpr:
        """
        Dispatch method that transforms XPath to support the pseudo-element.
        """
        if isinstance(pseudo_element, FunctionalPseudoElement):
            method_name = f"xpath_{pseudo_element.name.replace('-', '_')}_functional_pseudo_element"
            method = getattr(self, method_name, None)
            if not method:  # pragma: no cover
                raise ExpressionError(f"The functional pseudo-element ::{pseudo_element.name}() is unknown")
            xpath = method(xpath, pseudo_element)
        else:
            method_name = f"xpath_{pseudo_element.replace('-', '_')}_simple_pseudo_element"
            method = getattr(self, method_name, None)
            if not method:  # pragma: no cover
                raise ExpressionError(f"The pseudo-element ::{pseudo_element} is unknown")
            xpath = method(xpath)
        return xpath

    @staticmethod
    def xpath_attr_functional_pseudo_element(xpath: OriginalXPathExpr, function: FunctionalPseudoElement) -> XPathExpr:
        """Support selecting attribute values using ::attr() pseudo-element"""
        if function.argument_types() not in (["STRING"], ["IDENT"]):  # pragma: no cover
            raise ExpressionError(f"Expected a single string or ident for ::attr(), got {function.arguments!r}")
        return XPathExpr.from_xpath(xpath, attribute=function.arguments[0].value)

    @staticmethod
    def xpath_text_simple_pseudo_element(xpath: OriginalXPathExpr) -> XPathExpr:
        """Support selecting text nodes using ::text pseudo-element"""
        return XPathExpr.from_xpath(xpath, textnode=True)


class HTMLTranslator(TranslatorMixin, OriginalHTMLTranslator):
    def css_to_xpath(self, css: str, prefix: str = "descendant-or-self::") -> str:

View on GitHub (pinned to 5d213a2d47)

Solutions

  1. Remove rendering-only pseudo-elements (`::before`, `::after`, `::placeholder`, `::first-line`, ...) from the selector — their content is not in the HTML tree.
  2. To get `::before/::after` content, read the page's CSS (or rendered styles via the browser engine) instead of the DOM.
  3. Use `::text` for text nodes and `::attr(name)` for attributes — these are the two Scrapling adds.
  4. Test selectors in isolation (`Selector('<html>...</html>').css(sel)`) to find the offending one quickly.

Example fix

# before
page.css('button::before')  # ExpressionError: unknown pseudo-element

# after
btn = page.css_first('button')
css_content = btn.pseudo_before_content  # or inspect stylesheet / use browser evaluation
Defensive patterns

Strategy: validation

Validate before calling

import re

SUPPORTED_SIMPLE = {'text'}

def check_simple_pseudos(css: str) -> list[str]:
    return [name for name in re.findall(r'::([a-zA-Z-]+)(?!\()', css) if name not in SUPPORTED_SIMPLE]

Try / catch

try:
    txt = page.css('p::first-line')
except Exception as e:
    if 'unknown' in str(e) and '::' in str(e):
        txt = page.css('p::text')  # fall back to the full text node
    else:
        raise

Prevention

When it happens

Trigger: `selector.css('p::first-line')`, `selector.css('input::placeholder')`, `selector.css('q::before')` — any `::<name>` without parentheses other than `::text` (and `::attr()` which is functional).

Common situations: Selectors copied from CSS stylesheets or DevTools that target visual pseudo-elements; expecting `::before`/`::after` content to be extractable (it lives in CSS, not the DOM); migrating from BeautifulSoup/lxml code that silently ignored unknown pseudo-elements.

Related errors


AI-assisted analysis of D4Vinci/Scrapling@5d213a2d47 (2026-08-14). Data as JSON: /api/errors/bca03ce4deb7a3f0. Report an issue: GitHub.