mirror of https://github.com/scrapy/scrapy.git
Merge pull request #6527 from Gallaecio/dont-throttle-meta
Replace Slot.throttle with Request.meta['dont_throttle']
This commit is contained in:
commit
bcef96570b
|
|
@ -21,9 +21,14 @@ Design goals
|
|||
How it works
|
||||
============
|
||||
|
||||
AutoThrottle extension adjusts download delays dynamically to make spider send
|
||||
:setting:`AUTOTHROTTLE_TARGET_CONCURRENCY` concurrent requests on average
|
||||
to each remote website.
|
||||
Scrapy allows defining the concurrency and delay of different download slots,
|
||||
e.g. through the :setting:`DOWNLOAD_SLOTS` setting. By default requests are
|
||||
assigned to slots based on their URL domain, although it is possible to
|
||||
customize the download slot of any request.
|
||||
|
||||
The AutoThrottle extension adjusts the delay of each download slot dynamically,
|
||||
to make your spider send :setting:`AUTOTHROTTLE_TARGET_CONCURRENCY` concurrent
|
||||
requests on average to each remote website.
|
||||
|
||||
It uses download latency to compute the delays. The main idea is the
|
||||
following: if a server needs ``latency`` seconds to respond, a client
|
||||
|
|
@ -47,18 +52,6 @@ effect, but there are some important differences:
|
|||
|
||||
AutoThrottle doesn't have these issues.
|
||||
|
||||
Disabling throttling on a downloader slot
|
||||
=========================================
|
||||
|
||||
It is possible to disable AutoThrottle for a specific download slot at run time
|
||||
by setting its ``throttle`` attribute to ``False``, e.g. using
|
||||
:setting:`DOWNLOAD_SLOTS`.
|
||||
|
||||
Note, however, that AutoThrottle still determines the starting delay of every
|
||||
slot by setting the ``download_delay`` attribute on the running spider. You
|
||||
might want to set a custom value for the ``delay`` attribute of the slot, e.g.
|
||||
using :setting:`DOWNLOAD_SLOTS`.
|
||||
|
||||
Throttling algorithm
|
||||
====================
|
||||
|
||||
|
|
@ -92,6 +85,33 @@ callback, for example, and unable to attend downloads. However, these latencies
|
|||
should still give a reasonable estimate of how busy Scrapy (and ultimately, the
|
||||
server) is, and this extension builds on that premise.
|
||||
|
||||
.. reqmeta:: autothrottle_dont_adjust_delay
|
||||
|
||||
Prevent specific requests from triggering slot delay adjustments
|
||||
================================================================
|
||||
|
||||
AutoThrottle adjusts the delay of download slots based on the latencies of
|
||||
responses that belong to that download slot. The only exceptions are non-200
|
||||
responses, which are only taken into account to increase that delay, but
|
||||
ignored if they would decrease that delay.
|
||||
|
||||
You can also set the ``autothrottle_dont_adjust_delay`` request metadata key to
|
||||
``True`` in any request to prevent its response latency from impacting the
|
||||
delay of its download slot:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from scrapy import Request
|
||||
|
||||
Request("https://example.com", meta={"autothrottle_dont_adjust_delay": True})
|
||||
|
||||
Note, however, that AutoThrottle still determines the starting delay of every
|
||||
download slot by setting the ``download_delay`` attribute on the running
|
||||
spider. If you want AutoThrottle not to impact a download slot at all, in
|
||||
addition to setting this meta key in all requests that use that download slot,
|
||||
you might want to set a custom value for the ``delay`` attribute of that
|
||||
download slot, e.g. using :setting:`DOWNLOAD_SLOTS`.
|
||||
|
||||
Settings
|
||||
========
|
||||
|
||||
|
|
|
|||
|
|
@ -668,6 +668,7 @@ are some special keys recognized by Scrapy and its built-in extensions.
|
|||
|
||||
Those are:
|
||||
|
||||
* :reqmeta:`autothrottle_dont_adjust_delay`
|
||||
* :reqmeta:`bindaddress`
|
||||
* :reqmeta:`cookiejar`
|
||||
* :reqmeta:`dont_cache`
|
||||
|
|
|
|||
|
|
@ -845,12 +845,7 @@ Allows to define concurrency/delay parameters on per slot (domain) basis:
|
|||
.. code-block:: python
|
||||
|
||||
DOWNLOAD_SLOTS = {
|
||||
"quotes.toscrape.com": {
|
||||
"concurrency": 1,
|
||||
"delay": 2,
|
||||
"randomize_delay": False,
|
||||
"throttle": False,
|
||||
},
|
||||
"quotes.toscrape.com": {"concurrency": 1, "delay": 2, "randomize_delay": False},
|
||||
"books.toscrape.com": {"delay": 3, "randomize_delay": False},
|
||||
}
|
||||
|
||||
|
|
@ -862,9 +857,6 @@ Allows to define concurrency/delay parameters on per slot (domain) basis:
|
|||
- :setting:`CONCURRENT_REQUESTS_PER_DOMAIN`: ``concurrency``
|
||||
- :setting:`RANDOMIZE_DOWNLOAD_DELAY`: ``randomize_delay``
|
||||
|
||||
There is no global setting for ``throttle``, whose default value is
|
||||
``None``.
|
||||
|
||||
|
||||
.. setting:: DOWNLOAD_TIMEOUT
|
||||
|
||||
|
|
|
|||
|
|
@ -36,13 +36,10 @@ class Slot:
|
|||
concurrency: int,
|
||||
delay: float,
|
||||
randomize_delay: bool,
|
||||
*,
|
||||
throttle: bool | None = None,
|
||||
):
|
||||
self.concurrency: int = concurrency
|
||||
self.delay: float = delay
|
||||
self.randomize_delay: bool = randomize_delay
|
||||
self.throttle = throttle
|
||||
|
||||
self.active: set[Request] = set()
|
||||
self.queue: deque[tuple[Request, Deferred[Response]]] = deque()
|
||||
|
|
@ -67,15 +64,13 @@ class Slot:
|
|||
return (
|
||||
f"{cls_name}(concurrency={self.concurrency!r}, "
|
||||
f"delay={self.delay:.2f}, "
|
||||
f"randomize_delay={self.randomize_delay!r}, "
|
||||
f"throttle={self.throttle!r})"
|
||||
f"randomize_delay={self.randomize_delay!r})"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f"<downloader.Slot concurrency={self.concurrency!r} "
|
||||
f"delay={self.delay:.2f} randomize_delay={self.randomize_delay!r} "
|
||||
f"throttle={self.throttle!r} "
|
||||
f"len(active)={len(self.active)} len(queue)={len(self.queue)} "
|
||||
f"len(transferring)={len(self.transferring)} "
|
||||
f"lastseen={datetime.fromtimestamp(self.lastseen).isoformat()}>"
|
||||
|
|
@ -146,8 +141,7 @@ class Downloader:
|
|||
slot_settings.get("delay", delay),
|
||||
)
|
||||
randomize_delay = slot_settings.get("randomize_delay", self.randomize_delay)
|
||||
throttle = slot_settings.get("throttle", None)
|
||||
new_slot = Slot(conc, delay, randomize_delay, throttle=throttle)
|
||||
new_slot = Slot(conc, delay, randomize_delay)
|
||||
self.slots[key] = new_slot
|
||||
|
||||
return key, self.slots[key]
|
||||
|
|
|
|||
|
|
@ -64,7 +64,11 @@ class AutoThrottle:
|
|||
) -> None:
|
||||
key, slot = self._get_slot(request, spider)
|
||||
latency = request.meta.get("download_latency")
|
||||
if latency is None or slot is None or slot.throttle is False:
|
||||
if (
|
||||
latency is None
|
||||
or slot is None
|
||||
or request.meta.get("autothrottle_dont_adjust_delay", False) is True
|
||||
):
|
||||
return
|
||||
|
||||
olddelay = slot.delay
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@ class SlotTest(unittest.TestCase):
|
|||
slot = Slot(concurrency=8, delay=0.1, randomize_delay=True)
|
||||
self.assertEqual(
|
||||
repr(slot),
|
||||
"Slot(concurrency=8, delay=0.10, randomize_delay=True, throttle=None)",
|
||||
"Slot(concurrency=8, delay=0.10, randomize_delay=True)",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -80,7 +80,6 @@ def test_params():
|
|||
"concurrency": 1,
|
||||
"delay": 2,
|
||||
"randomize_delay": False,
|
||||
"throttle": False,
|
||||
}
|
||||
settings = {
|
||||
"DOWNLOAD_SLOTS": {
|
||||
|
|
|
|||
|
|
@ -157,17 +157,24 @@ def test_startdelay_definition(min_spider, min_setting, start_setting, expected)
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("meta", "slot", "throttle"),
|
||||
("meta", "slot"),
|
||||
(
|
||||
({}, None, None),
|
||||
({"download_latency": 1.0}, None, None),
|
||||
({"download_slot": "foo"}, None, None),
|
||||
({"download_slot": "foo"}, "foo", None),
|
||||
({"download_latency": 1.0, "download_slot": "foo"}, None, None),
|
||||
({"download_latency": 1.0, "download_slot": "foo"}, "foo", False),
|
||||
({}, None),
|
||||
({"download_latency": 1.0}, None),
|
||||
({"download_slot": "foo"}, None),
|
||||
({"download_slot": "foo"}, "foo"),
|
||||
({"download_latency": 1.0, "download_slot": "foo"}, None),
|
||||
(
|
||||
{
|
||||
"download_latency": 1.0,
|
||||
"download_slot": "foo",
|
||||
"autothrottle_dont_adjust_delay": True,
|
||||
},
|
||||
"foo",
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_skipped(meta, slot, throttle):
|
||||
def test_skipped(meta, slot):
|
||||
crawler = get_crawler()
|
||||
at = build_from_crawler(AutoThrottle, crawler)
|
||||
spider = TestSpider()
|
||||
|
|
@ -178,9 +185,7 @@ def test_skipped(meta, slot, throttle):
|
|||
crawler.engine.downloader = Mock()
|
||||
crawler.engine.downloader.slots = {}
|
||||
if slot is not None:
|
||||
_slot = Mock()
|
||||
_slot.throttle = throttle
|
||||
crawler.engine.downloader.slots[slot] = _slot
|
||||
crawler.engine.downloader.slots[slot] = object()
|
||||
at._adjust_delay = None # Raise exception if called.
|
||||
|
||||
at._response_downloaded(None, request, spider)
|
||||
|
|
|
|||
Loading…
Reference in New Issue