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
- Use exactly one attribute name: `a::attr(href)` or `a::attr('href')`.
- To get several attributes, issue separate queries (`css('a::attr(href)')` and `css('a::attr(title)')`) or select elements and read `element.attrib`.
- If the selector is built dynamically, validate/interpolate the attribute name before building the string.
- 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
- Always pass one attribute name: ::attr(href) or ::attr('href').
- Sanitize dynamically interpolated attribute names (non-empty, valid ident).
- Query attributes separately instead of comma-listing inside ::attr().
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
- The functional pseudo-element ::{pseudo_element.name}() is u
- The pseudo-element ::{pseudo_element} is unknown
- Expressions of type {__name__}.XPathExpr can ony join expres
- Storage system must implement `save` method
- Storage system must implement `retrieve` method
AI-assisted analysis of D4Vinci/Scrapling@5d213a2d47 (2026-08-14).
Data as JSON: /api/errors/7589e38dbfcaf63f.
Report an issue: GitHub.