Second
... ...Fourth
... ... ... """, ... encoding="utf-8", ... ) ``` -------------------------------- ### Configure Scrapy Logging Source: https://docs.scrapy.org/en/latest/_modules/scrapy/utils/log.html Initializes Scrapy's logging defaults, routes warnings and Twisted logs, and optionally installs a root handler based on settings. Use this early in your project setup. ```python from scrapy.settings import Settings, _SettingsKey def configure_logging( settings: Settings | dict[_SettingsKey, Any] | None = None, install_root_handler: bool = True, ) -> None: """ Initialize logging defaults for Scrapy. :param settings: settings used to create and configure a handler for the root logger (default: None). :type settings: dict, :class:`~scrapy.settings.Settings` object or ``None`` :param install_root_handler: whether to install root logging handler (default: True) :type install_root_handler: bool This function does: - Route warnings and twisted logging through Python standard logging - Assign DEBUG and ERROR level to Scrapy and Twisted loggers respectively - Route stdout to log if LOG_STDOUT setting is True When ``install_root_handler`` is True (default), this function also creates a handler for the root logger according to given settings (see :ref:`topics-logging-settings`). You can override default options using ``settings`` argument. When ``settings`` is empty or None, defaults are used. """ if not sys.warnoptions: # Route warnings through python logging logging.captureWarnings(True) observer = twisted_log.PythonLoggingObserver("twisted") observer.start() dictConfig(DEFAULT_LOGGING) if isinstance(settings, dict) or settings is None: settings = Settings(settings) if settings.getbool("LOG_STDOUT"): sys.stdout = StreamLogger(logging.getLogger("stdout")) if install_root_handler: install_scrapy_root_handler(settings) ``` -------------------------------- ### Configure Periodic Log Extension Settings Source: https://docs.scrapy.org/en/latest/topics/extensions.html Example configuration for the PeriodicLog extension, showing how to enable specific stats, delta calculations, and timing data. ```python custom_settings = { "LOG_LEVEL": "INFO", "PERIODIC_LOG_STATS": { "include": ["downloader/", "scheduler/", "log_count/", "item_scraped_count/"], }, "PERIODIC_LOG_DELTA": {"include": ["downloader/"]}, "PERIODIC_LOG_TIMING_ENABLED": True, "EXTENSIONS": { "scrapy.extensions.periodic_log.PeriodicLog": 0, }, } ``` -------------------------------- ### Example Items for Export Source: https://docs.scrapy.org/en/latest/topics/exporters.html These are example items used in the output examples for Scrapy's built-in Item Exporters. ```python Item(name="Color TV", price="1200") Item(name="DVD player", price="200") ``` -------------------------------- ### Install Scrapy in a Virtual Environment on Ubuntu Source: https://docs.scrapy.org/en/latest/intro/install.html After installing the necessary non-Python dependencies, install Scrapy within a virtual environment using pip. ```bash pip install scrapy ``` -------------------------------- ### Example Output Directory Structure with Batching Source: https://docs.scrapy.org/en/latest/topics/feed-exports.html Illustrates the directory structure and file naming convention when using FEED_EXPORT_BATCH_ITEM_COUNT and URI placeholders. ```text ->projectname -->dirname --->1-filename2020-03-28T14-45-08.237134.json --->2-filename2020-03-28T14-45-09.148903.json --->3-filename2020-03-28T14-45-10.046092.json ``` -------------------------------- ### Verify Installed Reactor Source: https://docs.scrapy.org/en/latest/_modules/scrapy/utils/reactor.html Checks if the currently installed Twisted reactor matches a specified import path. Raises a RuntimeError if no reactor is installed or if the types do not match. ```python def verify_installed_reactor(reactor_path: str) -> None: """Raise :exc:`RuntimeError` if the installed :mod:`~twisted.internet.reactor` does not match the specified import path or if no reactor is installed.""" if not is_reactor_installed(): raise RuntimeError( "verify_installed_reactor() called without an installed reactor." ) from twisted.internet import reactor expected_reactor_type = load_object(reactor_path) reactor_type = type(reactor) if not reactor_type == expected_reactor_type: raise RuntimeError( f"The installed reactor ({global_object_name(reactor_type)}) " f"does not match the requested one ({reactor_path})" ) ``` -------------------------------- ### Check if Asyncio Reactor is Installed Source: https://docs.scrapy.org/en/latest/_modules/scrapy/utils/reactor.html Verifies if the installed Twisted reactor is specifically an AsyncioSelectorReactor. Raises a RuntimeError if no reactor is installed, as per Scrapy's current behavior. ```python def is_asyncio_reactor_installed() -> bool: """Check whether the installed reactor is :class:`~twisted.internet.asyncioreactor.AsyncioSelectorReactor`. Raise a :exc:`RuntimeError` if no reactor is installed. In a future Scrapy version, when Scrapy supports running without a Twisted reactor, this function won't be useful for checking if it's possible to use asyncio features, so the code that that doesn't directly require a Twisted reactor should use :func:`scrapy.utils.asyncio.is_asyncio_available` instead of this function. .. versionchanged:: 2.13 In earlier Scrapy versions this function silently installed the default reactor if there was no reactor installed. Now it raises an exception to prevent silent problems in this case. """ if not is_reactor_installed(): raise RuntimeError( "is_asyncio_reactor_installed() called without an installed reactor." ) from twisted.internet import reactor return isinstance(reactor, asyncioreactor.AsyncioSelectorReactor) ``` -------------------------------- ### install_reactor Source: https://docs.scrapy.org/en/latest/_modules/scrapy/utils/reactor.html Installs the Twisted reactor with the specified import path, optionally configuring the asyncio event loop if an asyncio reactor is used. ```APIDOC ## install_reactor ### Description Installs the :mod:`~twisted.internet.reactor` with the specified import path. It also installs the asyncio event loop with the specified import path if the asyncio reactor is enabled. ### Parameters - **reactor_path** (str) - The import path for the Twisted reactor class (e.g., 'twisted.internet.asyncioreactor.AsyncioSelectorReactor'). - **event_loop_path** (str | None, optional) - The import path for the asyncio event loop class. Defaults to None, which uses the default event loop policy. ### Behavior - If `reactor_path` points to `AsyncioSelectorReactor`, it configures the asyncio event loop policy (especially for Windows) and installs the asyncio reactor. - Otherwise, it loads the specified reactor class and installs it using its respective installer function. ``` -------------------------------- ### Start Scrapy Engine Asynchronously Source: https://docs.scrapy.org/en/latest/_modules/scrapy/core/engine.html Starts the execution engine asynchronously. It sets the start time, signals engine startup, and begins processing requests if a spider is available. ```python async def start_async(self, *, _start_request_processing: bool = True) -> None: """Start the execution engine. .. versionadded:: 2.14 """ if self._starting: raise RuntimeError("Engine already running") self.start_time = time() self._starting = True await self.signals.send_catch_log_async(signal=signals.engine_started) if self._stopping: # band-aid until https://github.com/scrapy/scrapy/issues/6916 return if _start_request_processing and self.spider is None: # require an opened spider when not run in scrapy shell return self.running = True self._closewait = Deferred() if _start_request_processing: coro = self._start_request_processing() if is_asyncio_available(): # not wrapping in a Deferred here to avoid https://github.com/twisted/twisted/issues/12470 # (can happen when this is cancelled, e.g. in test_close_during_start_iteration()) self._start_request_processing_awaitable = asyncio.ensure_future(coro) else: self._start_request_processing_awaitable = Deferred.fromCoroutine(coro) with contextlib.suppress(asyncio.exceptions.CancelledError): await maybe_deferred_to_future(self._closewait) ``` -------------------------------- ### DownloaderAwarePriorityQueue Initialization Source: https://docs.scrapy.org/en/latest/_modules/scrapy/pqueues.html Illustrates the initialization of DownloaderAwarePriorityQueue, including validation for CONCURRENT_REQUESTS_PER_IP and handling of slot_startprios. ```python def __init__( self, crawler: Crawler, downstream_queue_cls: type[QueueProtocol], key: str, slot_startprios: dict[str, Iterable[int]] | None = None, *, start_queue_cls: type[QueueProtocol] | None = None, ): if crawler.settings.getint("CONCURRENT_REQUESTS_PER_IP") != 0: raise ValueError( f'"{self.__class__}" does not support CONCURRENT_REQUESTS_PER_IP' ) if slot_startprios and not isinstance(slot_startprios, dict): raise ValueError( "DownloaderAwarePriorityQueue accepts " "``slot_startprios`` as a dict; " f"{slot_startprios.__class__!r} instance " "is passed. Most likely, it means the state is " "created by an incompatible priority queue. " "Only a crawl started with the same priority " "queue class can be resumed." ) self._downloader_interface: DownloaderInterface = DownloaderInterface(crawler) self.downstream_queue_cls: type[QueueProtocol] = downstream_queue_cls self._start_queue_cls: type[QueueProtocol] | None = start_queue_cls self.key: str = key self.crawler: Crawler = crawler self.pqueues: dict[str, ScrapyPriorityQueue] = {} # slot -> priority queue self._last_selected_slot: str | None = None if slot_startprios: for slot, startprios in slot_startprios.items(): self.pqueues[slot] = self.pqfactory(slot, startprios) ``` -------------------------------- ### Log using a Custom Logger Source: https://docs.scrapy.org/en/latest/topics/logging.html Demonstrates how to create and use a custom logger by specifying a name with `logging.getLogger`. This allows for independent configuration of loggers. ```python import logging logger = logging.getLogger("mycustomlogger") logger.warning("This is a warning") ``` -------------------------------- ### Start Twisted Reactor with Asyncio Integration Source: https://docs.scrapy.org/en/latest/_modules/scrapy/crawler.html Initializes and runs the Twisted reactor, integrating with asyncio for crawl task completion if configured. ```python def _start_twisted( self, stop_after_crawl: bool, install_signal_handlers: bool ) -> None: from twisted.internet import reactor if stop_after_crawl: loop = asyncio.get_event_loop() join_task = loop.create_task(self.join()) join_task.add_done_callback(self._stop_reactor) self._setup_reactor(install_signal_handlers) reactor.run(installSignalHandlers=install_signal_handlers) # blocking call ``` -------------------------------- ### Install Pympler Source: https://docs.scrapy.org/en/latest/topics/leaks.html Install the Pympler library, which includes muppy, using pip. ```bash pip install Pympler ``` -------------------------------- ### Verify Installed Asyncio Event Loop Source: https://docs.scrapy.org/en/latest/_modules/scrapy/utils/reactor.html Ensures that the event loop used by the installed Twisted reactor matches the specified import path. Raises a RuntimeError if no reactor is installed or if the loop class mismatches. ```python def verify_installed_asyncio_event_loop(loop_path: str) -> None: """Raise :exc:`RuntimeError` if the even loop of the installed :class:`~twisted.internet.asyncioreactor.AsyncioSelectorReactor` does not match the specified import path or if no reactor is installed.""" if not is_reactor_installed(): raise RuntimeError( "verify_installed_asyncio_event_loop() called without an installed reactor." ) from twisted.internet import reactor loop_class = load_object(loop_path) if isinstance(reactor._asyncioEventloop, loop_class): return installed = ( f"{reactor._asyncioEventloop.__class__.__module__}" f".{reactor._asyncioEventloop.__class__.__qualname__}" ) raise RuntimeError( "Scrapy found an asyncio Twisted reactor already " f"installed, and its event loop class ({installed}) does " "not match the one specified in the ASYNCIO_EVENT_LOOP " f"setting ({global_object_name(loop_class)}) ) ``` -------------------------------- ### follow Source: https://docs.scrapy.org/en/latest/_modules/scrapy/http/response/text.html Returns a Request instance to follow a link. Accepts arguments similar to Request.__init__. ```APIDOC ## follow ### Description Returns a :class:`~.Request` instance to follow a link ``url``. It accepts the same arguments as ``Request.__init__()`` method, but ``url`` can be not only an absolute URL, but also a relative URL, a :class:`~scrapy.link.Link` object, a :class:`~scrapy.Selector` object for a ```` or ```` element, or an attribute :class:`~scrapy.Selector`. See :ref:`response-follow-example` for usage examples. ### Method `follow( url: str | Link | parsel.Selector, callback: CallbackT | None = None, method: str = "GET", headers: Mapping[AnyStr, Any] | Iterable[tuple[AnyStr, Any]] | None = None, body: bytes | str | None = None, cookies: CookiesT | None = None, meta: dict[str, Any] | None = None, encoding: str | None = None, priority: int = 0, dont_filter: bool = False, errback: Callable[[Failure], Any] | None = None, cb_kwargs: dict[str, Any] | None = None, flags: list[str] | None = None, ) -> Request` ### Parameters #### Path Parameters None #### Query Parameters * **url** (str | Link | parsel.Selector) - Required - The URL to follow. Can be an absolute URL, relative URL, Link object, or a Selector. * **callback** (CallbackT | None) - Optional - The callback function to use for the request. * **method** (str) - Optional - The HTTP method to use for the request (default: "GET"). * **headers** (Mapping[AnyStr, Any] | Iterable[tuple[AnyStr, Any]] | None) - Optional - Headers for the request. * **body** (bytes | str | None) - Optional - Body for the request. * **cookies** (CookiesT | None) - Optional - Cookies for the request. * **meta** (dict[str, Any] | None) - Optional - Meta dictionary for the request. * **encoding** (str | None) - Optional - The encoding to use for the response (defaults to response's encoding). * **priority** (int) - Optional - Priority of the request (default: 0). * **dont_filter** (bool) - Optional - Whether to filter the request (default: False). * **errback** (Callable[[Failure], Any] | None) - Optional - The errback function to use for the request. * **cb_kwargs** (dict[str, Any] | None) - Optional - Keyword arguments to pass to the callback function. * **flags** (list[str] | None) - Optional - Flags for the request. ### Request Example None ### Response #### Success Response - **Request** - A Scrapy Request object. ### Response Example None ERROR HANDLING: - Raises ValueError if a SelectorList is passed as the url argument. ``` -------------------------------- ### Scrapy Redirect Middleware: Building GET Redirect Request Source: https://docs.scrapy.org/en/latest/_modules/scrapy/downloadermiddlewares/redirect.html Constructs a new request for redirection using the GET method. It ensures that request body and specific content-related headers are removed for the new GET request. ```python def _redirect_request_using_get(self, request: Request, response: Response, redirect_url: str) -> Request: redirect_request = self._build_redirect_request( request, response, url=redirect_url, method="GET", body="", ) redirect_request.headers.pop("Content-Type", None) redirect_request.headers.pop("Content-Length", None) redirect_request.headers.pop("Content-Encoding", None) redirect_request.headers.pop("Content-Language", None) redirect_request.headers.pop("Content-Location", None) return redirect_request ``` -------------------------------- ### Loading Default Settings Source: https://docs.scrapy.org/en/latest/_modules/scrapy/settings.html Illustrates how to load default settings from the Scrapy distribution. ```python from scrapy.settings import default_settings def get_default_settings(): return default_settings ``` -------------------------------- ### Getting Global Object Name Source: https://docs.scrapy.org/en/latest/_modules/scrapy/settings.html Demonstrates using global_object_name to get the string representation of an object. ```python from scrapy.utils.python import global_object_name # Example usage: # class MyClass: # pass # # obj = MyClass() # name = global_object_name(obj) ``` -------------------------------- ### Getting Downloader Slot Key Source: https://docs.scrapy.org/en/latest/_modules/scrapy/pqueues.html Delegates to the downloader to get the slot key for a given request. ```python def get_slot_key(self, request: Request) -> str: return self.downloader.get_slot_key(request) ``` -------------------------------- ### ItemLoader with Default Item Class Example Source: https://docs.scrapy.org/en/latest/_modules/itemloaders.html Demonstrates using the default_item_class for item instantiation. ```python from itemloaders import ItemLoader class MyItemLoader(ItemLoader): default_item_class = dict loader = MyItemLoader() print(isinstance(loader.item, dict)) ``` -------------------------------- ### Start Timeout Timer Source: https://docs.scrapy.org/en/latest/_modules/scrapy/extensions/closespider.html Starts a delayed call to close the spider after a specified timeout (CLOSESPIDER_TIMEOUT) when the spider is opened. ```python def spider_opened(self, spider: Spider) -> None: assert self.crawler.engine self.task = call_later( self.close_on["timeout"], self._close_spider, "closespider_timeout" ) ``` -------------------------------- ### Custom Log Formatter Example Source: https://docs.scrapy.org/en/latest/_modules/scrapy/logformatter.html Example of a custom LogFormatter that lowers the severity level for dropped item log messages. ```Python class PoliteLogFormatter(logformatter.LogFormatter): def dropped(self, item, exception, response, spider): return { 'level': logging.INFO, # lowering the level from logging.WARNING 'msg': "Dropped: %(exception)s" + os.linesep + "%("item")s", 'args': { 'exception': exception, 'item': item, } } ``` -------------------------------- ### JSON Exporter Setup Source: https://docs.scrapy.org/en/latest/_modules/scrapy/exporters.html Initializes a JSON exporter with optional indentation and encoding settings. Ensures JSON is properly formatted for readability. ```python self._kwargs.setdefault("indent", json_indent) self._kwargs.setdefault("ensure_ascii", not self.encoding) self.encoder = ScrapyJSONEncoder(**self._kwargs) self.first_item = True ``` -------------------------------- ### XMLFeedSpider Namespace Configuration Example Source: https://docs.scrapy.org/en/latest/topics/spiders.html This example demonstrates how to configure namespaces for an XMLFeedSpider, allowing iteration over nodes with specific prefixes and URIs. ```python class YourSpider(XMLFeedSpider): namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')] itertag = 'n:url' # ... ``` -------------------------------- ### Initialize FTP Files Store Source: https://docs.scrapy.org/en/latest/_modules/scrapy/pipelines/files.html Initializes the FTPFilesStore with FTP server details and credentials. It validates the URI scheme and extracts connection parameters. ```python def __init__(self, uri: str): if not uri.startswith("ftp://"): raise ValueError(f"Incorrect URI scheme in {uri}, expected 'ftp'") u = urlparse(uri) assert u.port assert u.hostname self.port: int = u.port self.host: str = u.hostname self.port = int(u.port or 21) assert self.FTP_USERNAME assert self.FTP_PASSWORD self.username: str = u.username or self.FTP_USERNAME self.password: str = u.password or self.FTP_PASSWORD self.basedir: str = u.path.rstrip("/") ``` -------------------------------- ### Initialize Google Cloud Storage Files Store Source: https://docs.scrapy.org/en/latest/_modules/scrapy/pipelines/files.html Initializes the GCSFilesStore with project ID, bucket, and prefix. It also checks for necessary IAM permissions. ```python def __init__(self, uri: str): from google.cloud import storage # noqa: PLC0415 client = storage.Client(project=self.GCS_PROJECT_ID) bucket, prefix = uri[5:].split("/", 1) self.bucket = client.bucket(bucket) self.prefix: str = prefix permissions = self.bucket.test_iam_permissions( ["storage.objects.get", "storage.objects.create"] ) if "storage.objects.get" not in permissions: logger.warning( "No 'storage.objects.get' permission for GSC bucket %(bucket)s. " "Checking if files are up to date will be impossible. Files will be downloaded every time.", {"bucket": bucket}, ) if "storage.objects.create" not in permissions: logger.error( "No 'storage.objects.create' permission for GSC bucket %(bucket)s. Saving files will be impossible!", {"bucket": bucket}, ) ``` -------------------------------- ### XMLFeedSpider Iteration Tag Example Source: https://docs.scrapy.org/en/latest/topics/spiders.html This example shows how to define the 'itertag' attribute for an XMLFeedSpider to specify the XML node name to iterate over. ```python itertag = 'product' ``` -------------------------------- ### Enable Scrapy Extensions Source: https://docs.scrapy.org/en/latest/topics/extensions.html Add extensions to the EXTENSIONS setting to enable them at startup. Priorities determine the loading order. ```python EXTENSIONS = { "scrapy.extensions.corestats.CoreStats": 500, "scrapy.extensions.telnet.TelnetConsole": 500, } ``` -------------------------------- ### Generate Coverage Report Source: https://docs.scrapy.org/en/latest/contributing.html Generate a coverage report for the tests. Ensure the `coverage` package is installed (`pip install coverage`) before running. ```bash coverage report ```