{"record":{"id":"354bd683faa81ba5","repo":"D4Vinci/Scrapling","slug":"text-nodes-do-not-have-attributes","errorCode":null,"errorMessage":"Text nodes do not have attributes","messagePattern":"Text nodes do not have attributes","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"scrapling/parser.py","lineNumber":185,"sourceCode":"                self._storage = _storage\n            else:\n                if not storage_args:\n                    storage_args = {\n                        \"storage_file\": __DEFAULT_DB_FILE__,\n                        \"url\": url,\n                    }\n\n                if not hasattr(storage, \"__wrapped__\"):\n                    raise ValueError(\"Storage class must be wrapped with lru_cache decorator, see docs for info\")\n\n                if not issubclass(storage.__wrapped__, StorageSystemMixin):  # pragma: no cover\n                    raise ValueError(\"Storage system must be inherited from class `StorageSystemMixin`\")\n\n                self._storage = storage(**storage_args)\n\n    def __getitem__(self, key: str) -> TextHandler:\n        if self._is_text_node(self._root):\n            raise TypeError(\"Text nodes do not have attributes\")\n        return self.attrib[key]\n\n    def __contains__(self, key: str) -> bool:\n        if self._is_text_node(self._root):\n            return False\n        return key in self.attrib\n\n    # Node functionalities, I wanted to move to a separate Mixin class, but it had a slight impact on performance\n    @staticmethod\n    def _is_text_node(\n        element: HtmlElement | _ElementUnicodeResult,\n    ) -> bool:\n        \"\"\"Return True if the given element is a result of a string expression\n        Examples:\n            XPath -> '/text()', '/@attribute', etc...\n            CSS3 -> '::text', '::attr(attrib)'...\n        \"\"\"\n        # Faster than checking `element.is_attribute or element.is_text or element.is_tail`","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/D4Vinci/Scrapling/blob/5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f/scrapling/parser.py#L167-L203","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"# before\nnode = page.xpath('//h1/text()').get_first()\nvalue = node['class']  # TypeError\n\n# after\nel = page.css('h1')[0]\nvalue = el['class']\ntext = page.css('h1::text').get()","handlingStrategy":"type-guard","validationCode":"def is_element_selector(sel) -> bool:\n    return not sel._is_text_node(sel._root)\n\nnode = page.xpath('//h1/text()').get_first()\nif is_element_selector(node):\n    value = node['class']\nelse:\n    value = None  # text node: no attributes","typeGuard":"from lxml.etree import _Element\n\ndef is_element_result(sel) -> bool:\n    \"\"\"True when the Selector wraps an element (has attributes), not a text node.\"\"\"\n    from lxml.etree import _ElementUnicodeResult\n    return not isinstance(sel._root, _ElementUnicodeResult)","tryCatchPattern":"try:\n    value = node['class']\nexcept TypeError as e:\n    if \"Text nodes do not have attributes\" in str(e):\n        value = None  # text node: skip attribute extraction\n    else:\n        raise","preventionTips":["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."],"tags":["parser","selector","xpath","text-node","type-error"],"backgroundTag":null,"analyzedSha":"5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f","analyzedAt":"2026-08-14T22:23:09.440Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}