<a id="topics-coroutines"></a>

# Coroutines

Scrapy [supports](#coroutine-support) the [coroutine syntax](https://docs.python.org/3/reference/compound_stmts.html#async)
(i.e. `async def`).

<a id="coroutine-support"></a>

## 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 [`start()`](spiders.md#scrapy.Spider.start) spider method, which *must* be
  defined as an [asynchronous generator](https://docs.python.org/3/glossary.html#term-asynchronous-generator).
  > [!WARNING]
  > Added in version 2.13.
- [`Request`](request-response.md#scrapy.Request) [callbacks](request-response.md#callbacks), which may
  also be defined as [asynchronous generators](https://docs.python.org/3/glossary.html#term-asynchronous-generator).
- The [`process_item()`](item-pipeline.md#process_item) method of
  [item pipelines](item-pipeline.md#topics-item-pipeline).
- The
  [`process_request()`](downloader-middleware.md#scrapy.downloadermiddlewares.DownloaderMiddleware.process_request),
  [`process_response()`](downloader-middleware.md#scrapy.downloadermiddlewares.DownloaderMiddleware.process_response),
  and
  [`process_exception()`](downloader-middleware.md#scrapy.downloadermiddlewares.DownloaderMiddleware.process_exception)
  methods of
  [downloader middlewares](downloader-middleware.md#topics-downloader-middleware-custom).
- The
  [`process_spider_output()`](spider-middleware.md#scrapy.spidermiddlewares.SpiderMiddleware.process_spider_output)
  method of [spider middlewares](spider-middleware.md#topics-spider-middleware), which
  *must* be defined as an [asynchronous generator](https://docs.python.org/3/glossary.html#term-asynchronous-generator) except in
  [universal spider middlewares](spider-middleware.md#universal-spider-middleware).
- The [`process_start()`](spider-middleware.md#scrapy.spidermiddlewares.SpiderMiddleware.process_start) method
  of [spider middlewares](spider-middleware.md#custom-spider-middleware), which *must* be
  defined as an [asynchronous generator](https://docs.python.org/3/glossary.html#term-asynchronous-generator).
  > [!WARNING]
  > Added in version 2.13.
- [Signal handlers that support deferreds](signals.md#signal-deferred).
- Methods of [download handlers](download-handlers.md#topics-download-handlers).
  > [!WARNING]
  > Added in version 2.14.

<a id="coroutine-deferred-apis"></a>

## Using Deferred-based APIs

In addition to native coroutine APIs Scrapy has some APIs that return a
[`Deferred`](https://docs.twisted.org/en/stable/api/twisted.internet.defer.Deferred.html) object or take a user-supplied
function that returns a [`Deferred`](https://docs.twisted.org/en/stable/api/twisted.internet.defer.Deferred.html) 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.

These APIs have a coroutine-based implementation and a Deferred-based one:

- [`scrapy.crawler.Crawler`](api.md#scrapy.crawler.Crawler):
  - [`crawl_async()`](api.md#scrapy.crawler.Crawler.crawl_async) (coroutine-based) and
    [`crawl()`](api.md#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.
- [`scrapy.crawler.AsyncCrawlerRunner`](api.md#scrapy.crawler.AsyncCrawlerRunner) and its subclass
  [`scrapy.crawler.AsyncCrawlerProcess`](api.md#scrapy.crawler.AsyncCrawlerProcess) (coroutine-based) and
  [`scrapy.crawler.CrawlerRunner`](api.md#scrapy.crawler.CrawlerRunner) and its subclass
  [`scrapy.crawler.CrawlerProcess`](api.md#scrapy.crawler.CrawlerProcess) (Deferred-based): the former
  doesn’t support non-default reactors and so the latter should be used
  with those.

The following user-supplied methods can return
[`Deferred`](https://docs.twisted.org/en/stable/api/twisted.internet.defer.Deferred.html) objects (the methods that can also
return coroutines are listed in [Supported callables](#coroutine-support)):

- Custom downloader implementations (see [`DOWNLOADER`](settings.md#std-setting-DOWNLOADER)):
  - `fetch()`
- Custom scheduler implementations (see [`SCHEDULER`](settings.md#std-setting-SCHEDULER)):
  - [`open()`](scheduler.md#scrapy.core.scheduler.BaseScheduler.open)
  - [`close()`](scheduler.md#scrapy.core.scheduler.BaseScheduler.close)
- Custom dupefilters (see [`DUPEFILTER_CLASS`](settings.md#std-setting-DUPEFILTER_CLASS)):
  - `open()`
  - `close()`
- Custom feed storages (see [`FEED_STORAGES`](feed-exports.md#std-setting-FEED_STORAGES)):
  - `store()`
- Subclasses of `scrapy.pipelines.media.MediaPipeline`:
  - `media_to_download()`
  - `item_completed()`
- Custom storages used by subclasses of
  [`scrapy.pipelines.files.FilesPipeline`](media-pipeline.md#scrapy.pipelines.files.FilesPipeline):
  - `persist_file()`
  - `stat_file()`

In most cases you can use these APIs in code that otherwise uses coroutines, by
wrapping a [`Deferred`](https://docs.twisted.org/en/stable/api/twisted.internet.defer.Deferred.html) object into a
[`Future`](https://docs.python.org/3/library/asyncio-future.html#asyncio.Future) object or vice versa. See [Integrating Deferred code and asyncio code](asyncio.md#asyncio-await-dfd) for
more information about this.

For example: a custom scheduler needs to define an `open()` method that can
return a [`Deferred`](https://docs.twisted.org/en/stable/api/twisted.internet.defer.Deferred.html) 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
[`deferred_f_from_coro_f()`](asyncio.md#scrapy.utils.defer.deferred_f_from_coro_f).

## 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:

```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:

```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
[awaitable objects](https://docs.python.org/3/glossary.html#term-awaitable) such as [`Future`](https://docs.python.org/3/library/asyncio-future.html#asyncio.Future).
This means you can use many useful Python libraries providing such code:

<!-- skip: next -->
```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](https://github.com/aio-libs), require the
> [`asyncio`](https://docs.python.org/3/library/asyncio.html#module-asyncio) loop and to use them you need to
> [enable asyncio support in Scrapy](asyncio.md).

> [!NOTE]
> If you want to `await` on Deferreds while using the asyncio reactor,
> you need to [wrap them](asyncio.md#asyncio-await-dfd).

Common use cases for asynchronous code include:

* requesting data from websites, databases and other services (in
  [`start()`](spiders.md#scrapy.Spider.start), callbacks, pipelines and
  middlewares);
* storing data in databases (in pipelines and middlewares);
* delaying the spider initialization until some external event (in the
  [`spider_opened`](signals.md#std-signal-spider_opened) handler);
* calling asynchronous Scrapy methods like
  `ExecutionEngine.download_async()` (see [the
  screenshot pipeline example](item-pipeline.md#screenshotpipeline)).

<a id="inline-requests"></a>

## Inline requests

The spider below shows how to send a request and await its response all from
within a spider callback:

```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:

```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(),
        }
```
