D4Vinci/Scrapling · error · RuntimeError
Cannot compare requests before generating their fingerprints
Error message
Cannot compare requests before generating their fingerprints!
What it means
Request.__eq__ compares requests by fingerprint, but the fingerprint (_fp) is computed lazily. If two Request objects are compared with == (or used in a set / as dict keys, which invokes __hash__ via the same state) before the fingerprint was ever generated, the comparison is impossible and raises RuntimeError.
Source
Thrown at scrapling/spiders/request.py:150
def __lt__(self, other: object) -> bool:
"""Compare requests by priority"""
if not isinstance(other, Request):
return NotImplemented
return self.priority < other.priority
def __gt__(self, other: object) -> bool:
"""Compare requests by priority"""
if not isinstance(other, Request):
return NotImplemented
return self.priority > other.priority
def __eq__(self, other: object) -> bool:
"""Requests are equal if they have the same fingerprint."""
if not isinstance(other, Request):
return NotImplemented
if self._fp is None or other._fp is None:
raise RuntimeError("Cannot compare requests before generating their fingerprints!")
return self._fp == other._fp
def __getstate__(self) -> dict[str, Any]:
"""Prepare state for pickling - store callback as name string for pickle compatibility."""
state = self.__dict__.copy()
state["_callback_name"] = getattr(self.callback, "__name__", None) if self.callback is not None else None
state["callback"] = None # Don't pickle the actual callable
return state
def __setstate__(self, state: dict[str, Any]) -> None:
"""Restore state from pickle - callback restored later via _restore_callback()."""
self._callback_name: str | None = state.pop("_callback_name", None)
self.__dict__.update(state)
def _restore_callback(self, spider: "Spider") -> None:
"""Restore callback from spider after unpickling.
:param spider: Spider instance to look up callback method onView on GitHub (pinned to 5d213a2d47)
Solutions
- Generate the fingerprint explicitly before comparing: _ = req_a.fingerprint; _ = req_b.fingerprint (or whatever the documented accessor/trigger is — accessing req.fingerprint populates _fp).
- In dedup code, key on req.fingerprint directly instead of relying on __eq__/sets of Request objects.
- If you control the flow, funnel requests through the framework's scheduling step first, which fingerprints them.
Example fix
# before if req_a == req_b: # RuntimeError if fingerprints not generated # after if req_a.fingerprint == req_b.fingerprint: # accessors compute _fp lazily
Defensive patterns
Strategy: validation
Validate before calling
# force lazy fingerprint generation before any comparison or set/dict use
_ = req_a.fingerprint
_ = req_b.fingerprint
seen = {req.fingerprint for req in requests} # key on fingerprint, not Request Type guard
def has_fingerprint(req) -> bool:
return getattr(req, '_fp', None) is not None Try / catch
try:
same = req_a == req_b
except RuntimeError:
_ = req_a.fingerprint, req_b.fingerprint
same = req_a == req_b Prevention
- Key dedup structures on req.fingerprint instead of Request objects.
- In tests, touch req.fingerprint once after construction before asserting equality.
When it happens
Trigger: Creating two Request objects and immediately checking req_a == req_b, or putting fresh Requests into a set(), without touching req.fingerprint (or a scheduler step) first.
Common situations: Writing unit tests for dedup logic with hand-built Request objects; grouping requests manually before handing them to the spider scheduler that would normally generate fingerprints.
Related errors
AI-assisted analysis of D4Vinci/Scrapling@5d213a2d47 (2026-08-14).
Data as JSON: /api/errors/ad99e87d09c71e57.
Report an issue: GitHub.