Source code for scrapy.spidermiddlewares.metacopy
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from scrapy.spidermiddlewares.base import BaseSpiderMiddleware
if TYPE_CHECKING:
from scrapy.crawler import Crawler
from scrapy.http import Request, Response
logger = logging.getLogger(__name__)
[docs]
class MetaCopyDetectionMiddleware(BaseSpiderMiddleware):
"""Warn when a spider yields a request with internal meta keys that should
not be copied from response.meta, or when two requests share the same meta
dict object.
Each warning is emitted at most once per crawl.
"""
_INTERNAL_KEYS: frozenset[str] = frozenset(
{
"_auth_proxy",
"_dont_cache",
"_scheme_proxy",
"download_latency",
"redirect_reasons",
"redirect_times",
"redirect_ttl",
"redirect_urls",
"retry_times",
}
)
def __init__(self, crawler: Crawler) -> None:
super().__init__(crawler)
skip = frozenset(crawler.settings.getlist("META_COPY_WARN_SKIP_KEYS", []))
self._keys: frozenset[str] = self._INTERNAL_KEYS - skip
self._warned: bool = False
def get_processed_request(
self, request: Request, response: Response | None
) -> Request | None:
if response is None:
return request
if not self._warned:
found = self._keys & request.meta.keys()
if found:
spider_name = type(self.crawler.spider).__name__
logger.warning(
f"{spider_name} yielded a request containing internal "
f"meta keys that were likely copied from response.meta "
f"and should not be forwarded to new requests: "
f"{sorted(found)}. See the MetaCopyDetectionMiddleware "
f"documentation for more information. Source response: "
f"{response}, target request: {request}",
extra={"spider": self.crawler.spider},
)
self._warned = True
return request