10 KiB
Coroutines
Scrapy :ref:`supports <coroutine-support>` the :ref:`coroutine syntax <async>` (i.e. async def).
System Message: ERROR/3 (<stdin>, line 7); backlink
Unknown interpreted text role "ref".System Message: ERROR/3 (<stdin>, line 7); backlink
Unknown interpreted text role "ref".Supported callables
The following callables may be defined as coroutines using async def, and hence use coroutine syntax (e.g. await, async for, async with):
The :meth:`~scrapy.Spider.start` spider method, which must be defined as an :term:`asynchronous generator`.
System Message: ERROR/3 (<stdin>, line 19); backlink
Unknown interpreted text role "meth".
System Message: ERROR/3 (<stdin>, line 19); backlink
Unknown interpreted text role "term".
System Message: ERROR/3 (<stdin>, line 22)
Unknown directive type "versionadded".
.. versionadded:: 2.13
:class:`~scrapy.Request` :ref:`callbacks <callbacks>`, which may also be defined as :term:`asynchronous generators <asynchronous generator>`.
System Message: ERROR/3 (<stdin>, line 24); backlink
Unknown interpreted text role "class".
System Message: ERROR/3 (<stdin>, line 24); backlink
Unknown interpreted text role "ref".
System Message: ERROR/3 (<stdin>, line 24); backlink
Unknown interpreted text role "term".
The :meth:`process_item` method of :ref:`item pipelines <topics-item-pipeline>`.
System Message: ERROR/3 (<stdin>, line 28); backlink
Unknown interpreted text role "meth".
System Message: ERROR/3 (<stdin>, line 28); backlink
Unknown interpreted text role "ref".
The :meth:`~scrapy.downloadermiddlewares.DownloaderMiddleware.process_request`, :meth:`~scrapy.downloadermiddlewares.DownloaderMiddleware.process_response`, and :meth:`~scrapy.downloadermiddlewares.DownloaderMiddleware.process_exception` methods of :ref:`downloader middlewares <topics-downloader-middleware-custom>`.
System Message: ERROR/3 (<stdin>, line 31); backlink
Unknown interpreted text role "meth".
System Message: ERROR/3 (<stdin>, line 31); backlink
Unknown interpreted text role "meth".
System Message: ERROR/3 (<stdin>, line 31); backlink
Unknown interpreted text role "meth".
System Message: ERROR/3 (<stdin>, line 31); backlink
Unknown interpreted text role "ref".
The :meth:`~scrapy.spidermiddlewares.SpiderMiddleware.process_spider_output` method of :ref:`spider middlewares <topics-spider-middleware>`, which must be defined as an :term:`asynchronous generator` except in :ref:`universal spider middlewares <universal-spider-middleware>`.
System Message: ERROR/3 (<stdin>, line 39); backlink
Unknown interpreted text role "meth".
System Message: ERROR/3 (<stdin>, line 39); backlink
Unknown interpreted text role "ref".
System Message: ERROR/3 (<stdin>, line 39); backlink
Unknown interpreted text role "term".
System Message: ERROR/3 (<stdin>, line 39); backlink
Unknown interpreted text role "ref".
The :meth:`~scrapy.spidermiddlewares.SpiderMiddleware.process_start` method of :ref:`spider middlewares <custom-spider-middleware>`, which must be defined as an :term:`asynchronous generator`.
System Message: ERROR/3 (<stdin>, line 45); backlink
Unknown interpreted text role "meth".
System Message: ERROR/3 (<stdin>, line 45); backlink
Unknown interpreted text role "ref".
System Message: ERROR/3 (<stdin>, line 45); backlink
Unknown interpreted text role "term".
System Message: ERROR/3 (<stdin>, line 49)
Unknown directive type "versionadded".
.. versionadded:: 2.13
:ref:`Signal handlers that support deferreds <signal-deferred>`.
System Message: ERROR/3 (<stdin>, line 51); backlink
Unknown interpreted text role "ref".
Methods of :ref:`download handlers <topics-download-handlers>`.
System Message: ERROR/3 (<stdin>, line 53); backlink
Unknown interpreted text role "ref".
System Message: ERROR/3 (<stdin>, line 55)
Unknown directive type "versionadded".
.. versionadded:: 2.14
Using Deferred-based APIs
In addition to native coroutine APIs Scrapy has some APIs that return a :class:`~twisted.internet.defer.Deferred` object or take a user-supplied function that returns a :class:`~twisted.internet.defer.Deferred` object. These APIs are also asynchronous but don't yet support native async def syntax. In the future we plan to add support for the async def syntax to these APIs or replace them with other APIs where changing the existing ones isn't possible.
System Message: ERROR/3 (<stdin>, line 63); backlink
Unknown interpreted text role "class".System Message: ERROR/3 (<stdin>, line 63); backlink
Unknown interpreted text role "class".These APIs have a coroutine-based implementation and a Deferred-based one:
:class:`scrapy.crawler.Crawler`:
System Message: ERROR/3 (<stdin>, line 73); backlink
Unknown interpreted text role "class".
:meth:`~scrapy.crawler.Crawler.crawl_async` (coroutine-based) and :meth:`~scrapy.crawler.Crawler.crawl` (Deferred-based): the former may be inconvenient to use in Deferred-based code so both are available, this may change in a future Scrapy version.
System Message: ERROR/3 (<stdin>, line 75); backlink
Unknown interpreted text role "meth".
System Message: ERROR/3 (<stdin>, line 75); backlink
Unknown interpreted text role "meth".
:class:`scrapy.crawler.AsyncCrawlerRunner` and its subclass :class:`scrapy.crawler.AsyncCrawlerProcess` (coroutine-based) and :class:`scrapy.crawler.CrawlerRunner` and its subclass :class:`scrapy.crawler.CrawlerProcess` (Deferred-based): the former doesn't support non-default reactors and so the latter should be used with those.
System Message: ERROR/3 (<stdin>, line 80); backlink
Unknown interpreted text role "class".
System Message: ERROR/3 (<stdin>, line 80); backlink
Unknown interpreted text role "class".
System Message: ERROR/3 (<stdin>, line 80); backlink
Unknown interpreted text role "class".
System Message: ERROR/3 (<stdin>, line 80); backlink
Unknown interpreted text role "class".
The following user-supplied methods can return :class:`~twisted.internet.defer.Deferred` objects (the methods that can also return coroutines are listed in :ref:`coroutine-support`):
System Message: ERROR/3 (<stdin>, line 87); backlink
Unknown interpreted text role "class".System Message: ERROR/3 (<stdin>, line 87); backlink
Unknown interpreted text role "ref".Custom downloader implementations (see :setting:`DOWNLOADER`):
System Message: ERROR/3 (<stdin>, line 91); backlink
Unknown interpreted text role "setting".
- fetch()
Custom scheduler implementations (see :setting:`SCHEDULER`):
System Message: ERROR/3 (<stdin>, line 95); backlink
Unknown interpreted text role "setting".
:meth:`~scrapy.core.scheduler.BaseScheduler.open`
System Message: ERROR/3 (<stdin>, line 97); backlink
Unknown interpreted text role "meth".
:meth:`~scrapy.core.scheduler.BaseScheduler.close`
System Message: ERROR/3 (<stdin>, line 99); backlink
Unknown interpreted text role "meth".
Custom dupefilters (see :setting:`DUPEFILTER_CLASS`):
System Message: ERROR/3 (<stdin>, line 101); backlink
Unknown interpreted text role "setting".
- open()
- close()
Custom feed storages (see :setting:`FEED_STORAGES`):
System Message: ERROR/3 (<stdin>, line 107); backlink
Unknown interpreted text role "setting".
- store()
Subclasses of :class:`scrapy.pipelines.media.MediaPipeline`:
System Message: ERROR/3 (<stdin>, line 111); backlink
Unknown interpreted text role "class".
- media_to_download()
- item_completed()
Custom storages used by subclasses of :class:`scrapy.pipelines.files.FilesPipeline`:
System Message: ERROR/3 (<stdin>, line 117); backlink
Unknown interpreted text role "class".
- persist_file()
- stat_file()
In most cases you can use these APIs in code that otherwise uses coroutines, by wrapping a :class:`~twisted.internet.defer.Deferred` object into a :class:`~asyncio.Future` object or vice versa. See :ref:`asyncio-await-dfd` for more information about this.
System Message: ERROR/3 (<stdin>, line 124); backlink
Unknown interpreted text role "class".System Message: ERROR/3 (<stdin>, line 124); backlink
Unknown interpreted text role "class".System Message: ERROR/3 (<stdin>, line 124); backlink
Unknown interpreted text role "ref".For example: a custom scheduler needs to define an open() method that can return a :class:`~twisted.internet.defer.Deferred` object. You can write a method that works with Deferreds and returns one directly, or you can write a coroutine and convert it into a function that returns a Deferred with :func:`~scrapy.utils.defer.deferred_f_from_coro_f`.
System Message: ERROR/3 (<stdin>, line 129); backlink
Unknown interpreted text role "class".System Message: ERROR/3 (<stdin>, line 129); backlink
Unknown interpreted text role "func".General usage
There are several use cases for coroutines in Scrapy.
Code that would return Deferreds when written for previous Scrapy versions, such as downloader middlewares and signal handlers, can be rewritten to be shorter and cleaner:
System Message: WARNING/2 (<stdin>, line 145)
Cannot analyze code. Pygments package not found.
.. code-block:: python
from itemadapter import ItemAdapter
class DbPipeline:
def _update_item(self, data, item):
adapter = ItemAdapter(item)
adapter["field"] = data
return item
def process_item(self, item):
adapter = ItemAdapter(item)
dfd = db.get_some_data(adapter["id"])
dfd.addCallback(self._update_item, item)
return dfd
becomes:
System Message: WARNING/2 (<stdin>, line 164)
Cannot analyze code. Pygments package not found.
.. code-block:: python
from itemadapter import ItemAdapter
class DbPipeline:
async def process_item(self, item):
adapter = ItemAdapter(item)
adapter["field"] = await db.get_some_data(adapter["id"])
return item
Coroutines may be used to call asynchronous code. This includes other coroutines, functions that return Deferreds and functions that return :term:`awaitable objects <awaitable>` such as :class:`~asyncio.Future`. This means you can use many useful Python libraries providing such code:
System Message: ERROR/3 (<stdin>, line 175); backlink
Unknown interpreted text role "term".System Message: ERROR/3 (<stdin>, line 175); backlink
Unknown interpreted text role "class".System Message: WARNING/2 (<stdin>, line 181)
Cannot analyze code. Pygments package not found.
.. code-block:: python
class MySpiderDeferred(Spider):
# ...
async def parse(self, response):
additional_response = await treq.get("https://additional.url")
additional_data = await treq.content(additional_response)
# ... use response and additional_data to yield items and requests
class MySpiderAsyncio(Spider):
# ...
async def parse(self, response):
async with aiohttp.ClientSession() as session:
async with session.get("https://additional.url") as additional_response:
additional_data = await additional_response.text()
# ... use response and additional_data to yield items and requests
Note
Many libraries that use coroutines, such as aio-libs, require the :mod:`asyncio` loop and to use them you need to :doc:`enable asyncio support in Scrapy<asyncio>`.
System Message: ERROR/3 (<stdin>, line 199); backlink
Unknown interpreted text role "mod".System Message: ERROR/3 (<stdin>, line 199); backlink
Unknown interpreted text role "doc".Note
If you want to await on Deferreds while using the asyncio reactor, you need to :ref:`wrap them<asyncio-await-dfd>`.
System Message: ERROR/3 (<stdin>, line 203); backlink
Unknown interpreted text role "ref".Sometimes you need to call code that is not async-friendly at all, such as a third-party client library with no asynchronous support. Calling such code directly from a coroutine would block the underlying event loop, preventing Scrapy from doing any other work while it runs. To avoid this, you can wrap the blocking call with :func:`~scrapy.utils.decorators.inthread`, which runs it in a separate thread and returns an awaitable with the result:
System Message: ERROR/3 (<stdin>, line 206); backlink
Unknown interpreted text role "func".System Message: WARNING/2 (<stdin>, line 213)
Cannot analyze code. Pygments package not found.
.. code-block:: python
from scrapy.utils.decorators import inthread
@inthread
def blocking_call(value):
# e.g. a call into a library with no async support
return some_sync_client.get(value)
class MySpider(Spider):
async def parse(self, response):
result = await blocking_call(response.url)
# ... use result to yield items and requests
:func:`~scrapy.utils.decorators.inthread` uses :func:`asyncio.to_thread` when asyncio support is enabled, and Twisted's thread pool otherwise, so it works regardless of which reactor your project uses.
System Message: ERROR/3 (<stdin>, line 229); backlink
Unknown interpreted text role "func".System Message: ERROR/3 (<stdin>, line 229); backlink
Unknown interpreted text role "func".Common use cases for asynchronous code include:
requesting data from websites, databases and other services (in :meth:`~scrapy.Spider.start`, callbacks, pipelines and middlewares);
System Message: ERROR/3 (<stdin>, line 235); backlink
Unknown interpreted text role "meth".
storing data in databases (in pipelines and middlewares);
delaying the spider initialization until some external event (in the :signal:`spider_opened` handler);
System Message: ERROR/3 (<stdin>, line 239); backlink
Unknown interpreted text role "signal".
calling asynchronous Scrapy methods like :meth:`ExecutionEngine.download_async() <scrapy.core.engine.ExecutionEngine.download_async>` (see :ref:`the screenshot pipeline example <ScreenshotPipeline>`).
System Message: ERROR/3 (<stdin>, line 241); backlink
Unknown interpreted text role "meth".
System Message: ERROR/3 (<stdin>, line 241); backlink
Unknown interpreted text role "ref".
Inline requests
The spider below shows how to send a request and await its response all from within a spider callback:
System Message: WARNING/2 (<stdin>, line 257)
Cannot analyze code. Pygments package not found.
.. code-block:: python
from scrapy import Spider, Request
class SingleRequestSpider(Spider):
name = "single"
start_urls = ["https://example.org/product"]
async def parse(self, response, **kwargs):
additional_request = Request("https://example.org/price")
additional_response = await self.crawler.engine.download_async(
additional_request
)
yield {
"h1": response.css("h1").get(),
"price": additional_response.css("#price").get(),
}
You can also send multiple requests in parallel:
System Message: WARNING/2 (<stdin>, line 278)
Cannot analyze code. Pygments package not found.
.. code-block:: python
import asyncio
from scrapy import Spider, Request
class MultipleRequestsSpider(Spider):
name = "multiple"
start_urls = ["https://example.com/product"]
async def parse(self, response, **kwargs):
additional_requests = [
Request("https://example.com/price"),
Request("https://example.com/color"),
]
tasks = []
for r in additional_requests:
task = self.crawler.engine.download_async(r)
tasks.append(task)
responses = await asyncio.gather(*tasks)
yield {
"h1": response.css("h1::text").get(),
"price": responses[0].css(".price::text").get(),
"color": responses[1].css(".color::text").get(),
}