D4Vinci/Scrapling · error · TypeError
Text nodes do not have attributes
Error message
Text nodes do not have attributes
What it means
TypeError from Selector.__getitem__: you used item access (selector['attr']) on a Selector wrapping a text node rather than an element. Text nodes (the string results of XPath like /text()) have no attributes, so attribute lookup is refused with this message. The companion __contains__ simply returns False for text nodes instead of raising.
Source
Thrown at scrapling/parser.py:185
self._storage = _storage
else:
if not storage_args:
storage_args = {
"storage_file": __DEFAULT_DB_FILE__,
"url": url,
}
if not hasattr(storage, "__wrapped__"):
raise ValueError("Storage class must be wrapped with lru_cache decorator, see docs for info")
if not issubclass(storage.__wrapped__, StorageSystemMixin): # pragma: no cover
raise ValueError("Storage system must be inherited from class `StorageSystemMixin`")
self._storage = storage(**storage_args)
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]
def __contains__(self, key: str) -> bool:
if self._is_text_node(self._root):
return False
return key in self.attrib
# Node functionalities, I wanted to move to a separate Mixin class, but it had a slight impact on performance
@staticmethod
def _is_text_node(
element: HtmlElement | _ElementUnicodeResult,
) -> bool:
"""Return True if the given element is a result of a string expression
Examples:
XPath -> '/text()', '/@attribute', etc...
CSS3 -> '::text', '::attr(attrib)'...
"""
# Faster than checking `element.is_attribute or element.is_text or element.is_tail`View on GitHub (pinned to 5d213a2d47)
Solutions
- Query the element itself, then take text separately: el = page.css('h1')[0]; el['class']; text = page.css('h1::text').get().
- Before attribute access, check the node type: if not sel._is_text_node(sel._root): ... or guard with `if not sel.get('class') is None` style APIs on element selectors.
- Filter mixed results to elements first (e.g. skip results where .tag is None for text nodes).
Example fix
# before
node = page.xpath('//h1/text()').get_first()
value = node['class'] # TypeError
# after
el = page.css('h1')[0]
value = el['class']
text = page.css('h1::text').get() Defensive patterns
Strategy: type-guard
Validate before calling
def is_element_selector(sel) -> bool:
return not sel._is_text_node(sel._root)
node = page.xpath('//h1/text()').get_first()
if is_element_selector(node):
value = node['class']
else:
value = None # text node: no attributes Type guard
from lxml.etree import _Element
def is_element_result(sel) -> bool:
"""True when the Selector wraps an element (has attributes), not a text node."""
from lxml.etree import _ElementUnicodeResult
return not isinstance(sel._root, _ElementUnicodeResult) Try / catch
try:
value = node['class']
except TypeError as e:
if "Text nodes do not have attributes" in str(e):
value = None # text node: skip attribute extraction
else:
raise Prevention
- Take attributes from element selectors, text from /text() or ::text results.
- Keep element queries and text queries as separate variables.
- Guard attribute loops over mixed xpath results with a text-node check.
When it happens
Trigger: node = selector.xpath('//h1/text()')[0]; node['href'] — the selector wraps the text string, not the h1 element. Also element.css('::text').first['class'], or iterating results where some items are text nodes and treating them uniformly as elements.
Common situations: Applying element-style code to the output of /text() or ::text pseudo-element queries; generic attribute-copy loops over mixed node lists; refactoring css selectors and accidentally appending ::text.
Related errors
- content argument must be str or bytes, got {type(content)}
- Selector class needs HTML content, or root arguments to work
- Invalid XPath selector: {selector}
- Expressions of type {__name__}.XPathExpr can ony join expres
- Response meta should be dictionary but got {type(meta).__nam
AI-assisted analysis of D4Vinci/Scrapling@5d213a2d47 (2026-08-14).
Data as JSON: /api/errors/354bd683faa81ba5.
Report an issue: GitHub.