{"record":{"id":"5d81599624d93c6f","repo":"D4Vinci/Scrapling","slug":"self-class-name-must-implement-parse-nod","errorCode":null,"errorMessage":"{self.__class__.__name__} must implement parse_node() method","messagePattern":"(.+?) must implement parse_node\\(\\) method","errorType":"exception","errorClass":"NotImplementedError","httpStatus":null,"severity":"error","filePath":"scrapling/spiders/templates/feed.py","lineNumber":71,"sourceCode":"        except OSError as e:\n            self.logger.warning(f\"Failed to decompress feed: {e}\")\n            return\n\n        try:\n            root = etree.fromstring(body)\n        except etree.XMLSyntaxError as e:\n            self.logger.warning(f\"Failed to parse XML feed from {response.url}: {e}\")\n            return\n\n        for node in self._iter_nodes(root):\n            async for result in self.parse_node(response, node):\n                yield result\n\n    async def parse_node(\n        self, response: \"Response\", node: _Element\n    ) -> AsyncGenerator[Union[Dict[str, Any], Request, None], None]:\n        \"\"\"Override to process one feed node; `node` is a namespace-stripped `lxml` element.\"\"\"\n        raise NotImplementedError(f\"{self.__class__.__name__} must implement parse_node() method\")\n        yield  # Make this a generator for type checkers\n\n    def _wanted_tag(self) -> Tuple[Optional[str], str]:\n        \"\"\"Resolve `itertag` into a `(namespace uri or None, localname)` pair.\"\"\"\n        prefix, _, name = self.itertag.rpartition(\":\")\n        if not prefix:\n            return None, name\n        uri = dict(self.namespaces).get(prefix)\n        if not uri:\n            raise ValueError(f\"`itertag` prefix {prefix!r} is not defined in `namespaces`\")\n        return uri, name\n\n    def _iter_nodes(self, root: _Element) -> Iterator[_Element]:\n        uri, name = self._wanted_tag()\n        for el in root.iter():\n            if isinstance(el.tag, str):\n                qname = etree.QName(el.tag)\n                if qname.localname == name and (uri is None or qname.namespace == uri):","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/D4Vinci/Scrapling/blob/5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f/scrapling/spiders/templates/feed.py#L53-L89","documentation":"XMLFeedSpider-derived spiders must override parse_node(); the base implementation raises NotImplementedError. The engine calls parse_node(response, node) once per matched itertag element found in the feed.","triggerScenarios":"Subclassing the XML feed template, setting itertag/namespaces, but not implementing parse_node; renaming the method (e.g. parse_item) so the abstract base is what gets dispatched.","commonSituations":"Porting a Scrapy XMLFeedSpider and assuming a different hook name; scaffolding the template subclass without filling in the node handler.","solutions":["Implement `async def parse_node(self, response, node)` yielding items/Requests/None, where node is a namespace-stripped lxml element","Verify the exact method name and signature against the template docs before running"],"exampleFix":"// before\nclass Feed(XMLFeedSpider):\n    name = \"feed\"\n    itertag = \"item\"\n\n// after\nclass Feed(XMLFeedSpider):\n    name = \"feed\"\n    itertag = \"item\"\n\n    async def parse_node(self, response, node):\n        yield {\"id\": node.get(\"id\")}","handlingStrategy":"validation","validationCode":"assert Feed.parse_node is not XMLFeedSpider.parse_node, \"implement parse_node()\"","typeGuard":"def implements_parse_node(cls) -> bool:\n    return cls.parse_node is not XMLFeedSpider.parse_node","tryCatchPattern":null,"preventionTips":["Implement parse_node in every XML feed spider","Match the documented method name exactly"],"tags":["feed","xml","abstract-method","spider-template"],"backgroundTag":null,"analyzedSha":"5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f","analyzedAt":"2026-08-14T22:23:09.440Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}