"""Generic spider templates that build on the `Spider` base.""" from dataclasses import dataclass from scrapling.spiders.links import LinkExtractor from scrapling.spiders.request import Request from scrapling.spiders.spider import Spider from scrapling.core._types import ( TYPE_CHECKING, Any, AsyncGenerator, Callable, Dict, List, Optional, Union, ) if TYPE_CHECKING: from scrapling.engines.toolbelt.custom import Response __all__ = ["CrawlRule", "CrawlSpider"] ParseCallback = Callable[ ["Response"], AsyncGenerator[Union[Dict[str, Any], Request, None], None], ] ProcessRequestFn = Callable[[Request, "Response"], Request] @dataclass class CrawlRule: """Rule for `CrawlSpider`: extract links from a response and dispatch them. :param link_extractor: `LinkExtractor` that produces URLs from each response. :param callback: Bound method on the spider to call for each matched URL. Falls back to the spider's default ``parse()`` by default. :param priority: Override the priority of the requests that will be dispatched. :param process_request: Optional bound method to mutate each `Request` before it is yielded. Signature: ``(request, response) -> request``. Use it to add headers, change priority, or filter requests. """ link_extractor: LinkExtractor callback: Optional[ParseCallback] = None priority: Optional[int] = None process_request: Optional[ProcessRequestFn] = None class CrawlSpider(Spider): """A generic spider that can extract and follow links automatically based on crawl rules. Override `rules()` to return a list of `CrawlRule`s. You can start from it and override it as needed for more custom functionality, or just implement your own spider. """ def rules(self) -> List[CrawlRule]: """Override to define link-following rules.""" return [] async def parse(self, response: "Response") -> AsyncGenerator[Union[Dict[str, Any], Request, None], None]: for rule in self.rules(): for url in rule.link_extractor.extract(response): req = response.follow(url, callback=rule.callback) if rule.priority is not None: req.priority = rule.priority if rule.process_request is not None: req = rule.process_request(req, response) yield req