D4Vinci/Scrapling · error · ExpressionError

Expected a single string or ident for ::attr(), got {functio

Error message

Expected a single string or ident for ::attr(), got {function.arguments!r}

What it means

Raised by `xpath_attr_functional_pseudo_element` in scrapling/core/translator.py:113 when translating an `::attr(...)` pseudo-element. After cssselect parses the selector, the handler validates that `::attr` received exactly one argument of type STRING (quoted) or IDENT (bare name). Zero arguments, multiple arguments, or non-string tokens (e.g. `::attr(5)`, `::attr([href])`) fail the `argument_types()` check and raise `ExpressionError` with the parsed arguments shown.

Source

Thrown at scrapling/core/translator.py:113

        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:
        return super().css_to_xpath(css, prefix)


translator = HTMLTranslator()
# Using a function instead of the translator directly to avoid Pyright override error


@lru_cache(maxsize=256)

View on GitHub (pinned to 5d213a2d47)

Solutions

  1. Use exactly one attribute name: `a::attr(href)` or `a::attr('href')`.
  2. To get several attributes, issue separate queries (`css('a::attr(href)')` and `css('a::attr(title)')`) or select elements and read `element.attrib`.
  3. If the selector is built dynamically, validate/interpolate the attribute name before building the string.
  4. Simplify the selector until it parses to isolate which argument list is malformed.

Example fix

# before
vals = page.css('a::attr(href, title)')  # ExpressionError

# after
hrefs = page.css('a::attr(href)')
titles = page.css('a::attr(title)')
# or
for a in page.css('a'):
    print(a.attrib.get('href'), a.attrib.get('title'))
Defensive patterns

Strategy: validation

Validate before calling

import re

ATTR_RE = re.compile(r'::attr\(\s*(?:\"[^\"]+\"|'[^']+'|[A-Za-z_:-][\w:.-]*)\s*\)')

def attr_usage_ok(css: str) -> bool:
    """Every ::attr() must have exactly one string/ident argument."""
    for m in re.finditer(r'::attr\([^)]*\)', css):
        if not ATTR_RE.fullmatch(m.group(0)):
            return False
    return True

Try / catch

try:
    v = page.css(f'a::attr({name})')
except Exception as e:
    if '::attr()' in str(e):
        raise ValueError(f'bad attribute name {name!r} in selector') from e
    raise

Prevention

When it happens

Trigger: `selector.css('a::attr()')` (empty), `selector.css('a::attr(href, title)')` (two args), `selector.css('a::attr(123)')` (numeric token) — any `::attr(...)` whose argument list is not exactly one STRING or IDENT.

Common situations: Typos and unbalanced parentheses when hand-building selector strings; attempting to fetch multiple attributes in one pseudo-element; quoting mistakes like `::attr('href)` that make cssselect parse unexpected tokens; dynamically generated selectors from templates with missing values (`f"a::attr({attr_name})"` with `attr_name=''`).

Related errors


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