From c327a92e971411e50e49dded772a73b293f9f0a9 Mon Sep 17 00:00:00 2001 From: bulat Date: Tue, 9 May 2023 18:04:18 +0500 Subject: [PATCH 01/16] add additional requests examples. --- docs/topics/asyncio.rst | 47 +++++++++++++++++++++++++++++++++++++++++ scrapy/utils/defer.py | 10 +++++---- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst index 7713b1af1..7aa83f505 100644 --- a/docs/topics/asyncio.rst +++ b/docs/topics/asyncio.rst @@ -98,6 +98,53 @@ Futures. Scrapy provides two helpers for this: into your own code. +Async additional requests +===================== + +The spider below shows a single use-case of scraping page and gathering price from a separate url:: + + + class SingleRequestSpider(scrapy.Spider): + name = "single" + start_urls = ["https://example.org/product"] + + async def parse(self, response, **kwargs): + additional_request = scrapy.Request('https://example.org/price') + deferred = self.crawler.engine.download(additional_request) + additional_response = await maybe_deferred_to_future(deferred) + yield { + 'h1': response.css('h1').get(), + 'price': additional_response.css('#price').get(), + } + + +Spider with gathering batch requests:: + + class BatchRequestsSpider(scrapy.Spider): + name = "batch" + start_urls = ["https://example.com/product"] + + async def parse(self, response, **kwargs): + additional_requests = [ + scrapy.Request("https://example.com/price1"), + scrapy.Request("https://example.com/price2"), + ] + coroutines = [] + for r in additional_requests: + deffered = self.crawler.engine.download(r) + coroutines.append(maybe_deferred_to_future(deffered)) + + responses = await asyncio.gather( + *coroutines, return_exceptions=True + ) + yield { + 'h1': response.css('h1::text').get(), + 'price': responses[0].css('.price_color::text').get(), + 'price2': responses[1].css('.price_color::text').get(), + } + + + .. _enforce-asyncio-requirement: Enforcing asyncio as a requirement diff --git a/scrapy/utils/defer.py b/scrapy/utils/defer.py index d25ebbdf4..a46274fef 100644 --- a/scrapy/utils/defer.py +++ b/scrapy/utils/defer.py @@ -340,8 +340,9 @@ def deferred_to_future(d: Deferred) -> Future: class MySpider(Spider): ... async def parse(self, response): - d = treq.get('https://example.com/additional') - additional_response = await deferred_to_future(d) + additional_request = scrapy.Request('https://example.org/price') + deferred = self.crawler.engine.download(additional_request) + additional_response = await deferred_to_future(deferred) """ return d.asFuture(_get_asyncio_event_loop()) @@ -368,8 +369,9 @@ def maybe_deferred_to_future(d: Deferred) -> Union[Deferred, Future]: class MySpider(Spider): ... async def parse(self, response): - d = treq.get('https://example.com/additional') - extra_response = await maybe_deferred_to_future(d) + additional_request = scrapy.Request('https://example.org/price') + deferred = self.crawler.engine.download(additional_request) + additional_response = await maybe_deferred_to_future(deferred) """ if not is_asyncio_reactor_installed(): return d From a75231a1ecd9a08b31ea3c1d5b59e457ac85ccf2 Mon Sep 17 00:00:00 2001 From: bulat Date: Tue, 9 May 2023 18:57:43 +0500 Subject: [PATCH 02/16] fix underline. --- docs/topics/asyncio.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst index 7aa83f505..d46527cfc 100644 --- a/docs/topics/asyncio.rst +++ b/docs/topics/asyncio.rst @@ -99,7 +99,7 @@ Futures. Scrapy provides two helpers for this: Async additional requests -===================== +========================= The spider below shows a single use-case of scraping page and gathering price from a separate url:: From d32c6782347c97086e804da0da01f45622743198 Mon Sep 17 00:00:00 2001 From: bulat Date: Tue, 9 May 2023 19:02:34 +0500 Subject: [PATCH 03/16] Update description. --- docs/topics/asyncio.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst index d46527cfc..d439e0ab8 100644 --- a/docs/topics/asyncio.rst +++ b/docs/topics/asyncio.rst @@ -101,7 +101,7 @@ Futures. Scrapy provides two helpers for this: Async additional requests ========================= -The spider below shows a single use-case of scraping page and gathering price from a separate url:: +The spider below shows a single use-case of scraping a page and gathering a price from a separate URL:: class SingleRequestSpider(scrapy.Spider): @@ -118,7 +118,7 @@ The spider below shows a single use-case of scraping page and gathering price fr } -Spider with gathering batch requests:: +The spider gathering batch requests:: class BatchRequestsSpider(scrapy.Spider): name = "batch" From 99b0ece165ff27b70a6d7375a86bcc67da111df7 Mon Sep 17 00:00:00 2001 From: bulat Date: Tue, 9 May 2023 20:27:46 +0500 Subject: [PATCH 04/16] remove extra line. --- docs/topics/asyncio.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst index d439e0ab8..0dab0ac5e 100644 --- a/docs/topics/asyncio.rst +++ b/docs/topics/asyncio.rst @@ -144,7 +144,6 @@ The spider gathering batch requests:: } - .. _enforce-asyncio-requirement: Enforcing asyncio as a requirement From b1f4017788877ba2139f8621ecb5e821c62c111d Mon Sep 17 00:00:00 2001 From: bulat Date: Wed, 10 May 2023 15:34:58 +0500 Subject: [PATCH 05/16] Refactor batch sample. --- docs/topics/asyncio.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst index 0dab0ac5e..dc83148f5 100644 --- a/docs/topics/asyncio.rst +++ b/docs/topics/asyncio.rst @@ -126,8 +126,8 @@ The spider gathering batch requests:: async def parse(self, response, **kwargs): additional_requests = [ - scrapy.Request("https://example.com/price1"), - scrapy.Request("https://example.com/price2"), + scrapy.Request("https://example.com/price"), + scrapy.Request("https://example.com/color"), ] coroutines = [] for r in additional_requests: @@ -139,8 +139,8 @@ The spider gathering batch requests:: ) yield { 'h1': response.css('h1::text').get(), - 'price': responses[0].css('.price_color::text').get(), - 'price2': responses[1].css('.price_color::text').get(), + 'price': responses[0].css('.price::text').get(), + 'color': responses[1].css('color::text').get(), } From 87d10161cd413353eba2abf8ebdc2a8656927c43 Mon Sep 17 00:00:00 2001 From: bulat Date: Wed, 10 May 2023 15:35:48 +0500 Subject: [PATCH 06/16] Add selector as class. --- docs/topics/asyncio.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst index dc83148f5..f00ba0ff8 100644 --- a/docs/topics/asyncio.rst +++ b/docs/topics/asyncio.rst @@ -140,7 +140,7 @@ The spider gathering batch requests:: yield { 'h1': response.css('h1::text').get(), 'price': responses[0].css('.price::text').get(), - 'color': responses[1].css('color::text').get(), + 'color': responses[1].css('.color::text').get(), } From 57f3140daaa0166f924fcca42d3f3d3ef178bf92 Mon Sep 17 00:00:00 2001 From: Bulat Khabibullin Date: Wed, 10 May 2023 18:31:54 +0500 Subject: [PATCH 07/16] Update docs/topics/asyncio.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Adrián Chaves --- docs/topics/asyncio.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst index f00ba0ff8..f9efef108 100644 --- a/docs/topics/asyncio.rst +++ b/docs/topics/asyncio.rst @@ -101,8 +101,10 @@ Futures. Scrapy provides two helpers for this: Async additional requests ========================= -The spider below shows a single use-case of scraping a page and gathering a price from a separate URL:: +The spider below shows how to send a request and await its response all from +within a spider callback: +.. code-block:: python class SingleRequestSpider(scrapy.Spider): name = "single" From 26374e21f81eb53f5a21e1cc68a65e54fbb62cb6 Mon Sep 17 00:00:00 2001 From: Bulat Khabibullin Date: Wed, 10 May 2023 18:32:36 +0500 Subject: [PATCH 08/16] Update docs/topics/asyncio.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Adrián Chaves --- docs/topics/asyncio.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst index f9efef108..7fe78585a 100644 --- a/docs/topics/asyncio.rst +++ b/docs/topics/asyncio.rst @@ -119,8 +119,9 @@ within a spider callback: 'price': additional_response.css('#price').get(), } +You can also send multiple requests in parallel: -The spider gathering batch requests:: +.. code-block:: python class BatchRequestsSpider(scrapy.Spider): name = "batch" From 85103b493289011161731e4fafec4320cd85e0af Mon Sep 17 00:00:00 2001 From: Bulat Khabibullin Date: Thu, 11 May 2023 12:53:43 +0500 Subject: [PATCH 09/16] add proper example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Adrián Chaves --- docs/topics/asyncio.rst | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst index 7fe78585a..eeec76157 100644 --- a/docs/topics/asyncio.rst +++ b/docs/topics/asyncio.rst @@ -123,6 +123,8 @@ You can also send multiple requests in parallel: .. code-block:: python + from twisted.internet.defer import DeferredList + class BatchRequestsSpider(scrapy.Spider): name = "batch" start_urls = ["https://example.com/product"] @@ -132,14 +134,11 @@ You can also send multiple requests in parallel: scrapy.Request("https://example.com/price"), scrapy.Request("https://example.com/color"), ] - coroutines = [] + deferreds = [] for r in additional_requests: - deffered = self.crawler.engine.download(r) - coroutines.append(maybe_deferred_to_future(deffered)) - - responses = await asyncio.gather( - *coroutines, return_exceptions=True - ) + deferred = self.crawler.engine.download(r) + deferreds.append(deferred) + responses = await maybe_deferred_to_future(DeferredList(deferreds)) yield { 'h1': response.css('h1::text').get(), 'price': responses[0].css('.price::text').get(), From 6194db133518b07b92bfdae35332c69e95f5c415 Mon Sep 17 00:00:00 2001 From: bulat Date: Thu, 11 May 2023 12:54:01 +0500 Subject: [PATCH 10/16] Update title. --- docs/topics/asyncio.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst index 7fe78585a..1b670aaab 100644 --- a/docs/topics/asyncio.rst +++ b/docs/topics/asyncio.rst @@ -98,7 +98,7 @@ Futures. Scrapy provides two helpers for this: into your own code. -Async additional requests +Inline requests ========================= The spider below shows how to send a request and await its response all from From fc2d1b217130ebf605636049ea773196a50303a0 Mon Sep 17 00:00:00 2001 From: bulat Date: Thu, 11 May 2023 12:56:29 +0500 Subject: [PATCH 11/16] make example reachable. --- docs/topics/asyncio.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst index 12bf548df..fcf44c0cb 100644 --- a/docs/topics/asyncio.rst +++ b/docs/topics/asyncio.rst @@ -125,8 +125,8 @@ You can also send multiple requests in parallel: from twisted.internet.defer import DeferredList - class BatchRequestsSpider(scrapy.Spider): - name = "batch" + class MultipleRequestsSpider(scrapy.Spider): + name = "multiple" start_urls = ["https://example.com/product"] async def parse(self, response, **kwargs): @@ -141,8 +141,8 @@ You can also send multiple requests in parallel: responses = await maybe_deferred_to_future(DeferredList(deferreds)) yield { 'h1': response.css('h1::text').get(), - 'price': responses[0].css('.price::text').get(), - 'color': responses[1].css('.color::text').get(), + 'price': responses[0][1].css('.price::text').get(), + 'price2': responses[1][1].css('.color::text').get(), } From b62c1263de4b026e8529416d5dede1795d47f7ad Mon Sep 17 00:00:00 2001 From: bulat Date: Thu, 11 May 2023 13:15:30 +0500 Subject: [PATCH 12/16] add import to the example. --- docs/topics/asyncio.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst index fcf44c0cb..2ad784335 100644 --- a/docs/topics/asyncio.rst +++ b/docs/topics/asyncio.rst @@ -106,6 +106,8 @@ within a spider callback: .. code-block:: python + from scrapy.utils.defer import maybe_deferred_to_future + class SingleRequestSpider(scrapy.Spider): name = "single" start_urls = ["https://example.org/product"] From 4878cc7ef04ae40279d80fec5d5234d17569ce11 Mon Sep 17 00:00:00 2001 From: bulat Date: Thu, 11 May 2023 13:19:40 +0500 Subject: [PATCH 13/16] Add proper imports. --- docs/topics/asyncio.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst index 2ad784335..a3f45a84b 100644 --- a/docs/topics/asyncio.rst +++ b/docs/topics/asyncio.rst @@ -125,7 +125,7 @@ You can also send multiple requests in parallel: .. code-block:: python - from twisted.internet.defer import DeferredList + from scrapy.utils.defer import DeferredList class MultipleRequestsSpider(scrapy.Spider): name = "multiple" From 8de2064ba33d6e0b8e0a22a6b5f6928a35eb44b7 Mon Sep 17 00:00:00 2001 From: bulat Date: Thu, 11 May 2023 13:22:33 +0500 Subject: [PATCH 14/16] add import. --- docs/topics/asyncio.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst index a3f45a84b..5e0063be0 100644 --- a/docs/topics/asyncio.rst +++ b/docs/topics/asyncio.rst @@ -125,7 +125,7 @@ You can also send multiple requests in parallel: .. code-block:: python - from scrapy.utils.defer import DeferredList + from scrapy.utils.defer import DeferredList, maybe_deferred_to_future class MultipleRequestsSpider(scrapy.Spider): name = "multiple" From e4cf8fc121fc89d70949a9159bfe67cbd0429e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Chaves?= Date: Mon, 15 May 2023 18:51:58 +0200 Subject: [PATCH 15/16] Update asyncio.rst --- docs/topics/asyncio.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst index 5e0063be0..efb93c844 100644 --- a/docs/topics/asyncio.rst +++ b/docs/topics/asyncio.rst @@ -99,7 +99,7 @@ Futures. Scrapy provides two helpers for this: Inline requests -========================= +=============== The spider below shows how to send a request and await its response all from within a spider callback: From d362699fa3855c7fd6e11204ccd8668128e38a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Chaves?= Date: Tue, 16 May 2023 13:39:02 +0200 Subject: [PATCH 16/16] Move inline request examples to the coroutines documentation --- docs/topics/asyncio.rst | 50 --------------------------------- docs/topics/coroutines.rst | 57 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 50 deletions(-) diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst index efb93c844..7713b1af1 100644 --- a/docs/topics/asyncio.rst +++ b/docs/topics/asyncio.rst @@ -98,56 +98,6 @@ Futures. Scrapy provides two helpers for this: into your own code. -Inline requests -=============== - -The spider below shows how to send a request and await its response all from -within a spider callback: - -.. code-block:: python - - from scrapy.utils.defer import maybe_deferred_to_future - - class SingleRequestSpider(scrapy.Spider): - name = "single" - start_urls = ["https://example.org/product"] - - async def parse(self, response, **kwargs): - additional_request = scrapy.Request('https://example.org/price') - deferred = self.crawler.engine.download(additional_request) - additional_response = await maybe_deferred_to_future(deferred) - yield { - 'h1': response.css('h1').get(), - 'price': additional_response.css('#price').get(), - } - -You can also send multiple requests in parallel: - -.. code-block:: python - - from scrapy.utils.defer import DeferredList, maybe_deferred_to_future - - class MultipleRequestsSpider(scrapy.Spider): - name = "multiple" - start_urls = ["https://example.com/product"] - - async def parse(self, response, **kwargs): - additional_requests = [ - scrapy.Request("https://example.com/price"), - scrapy.Request("https://example.com/color"), - ] - deferreds = [] - for r in additional_requests: - deferred = self.crawler.engine.download(r) - deferreds.append(deferred) - responses = await maybe_deferred_to_future(DeferredList(deferreds)) - yield { - 'h1': response.css('h1::text').get(), - 'price': responses[0][1].css('.price::text').get(), - 'price2': responses[1][1].css('.color::text').get(), - } - - .. _enforce-asyncio-requirement: Enforcing asyncio as a requirement diff --git a/docs/topics/coroutines.rst b/docs/topics/coroutines.rst index 3916bd295..a65bab3ca 100644 --- a/docs/topics/coroutines.rst +++ b/docs/topics/coroutines.rst @@ -134,6 +134,63 @@ Common use cases for asynchronous code include: .. _aio-libs: https://github.com/aio-libs +.. _inline-requests: + +Inline requests +=============== + +The spider below shows how to send a request and await its response all from +within a spider callback: + +.. code-block:: python + + from scrapy import Spider, Request + from scrapy.utils.defer import maybe_deferred_to_future + + + class SingleRequestSpider(Spider): + name = "single" + start_urls = ["https://example.org/product"] + + async def parse(self, response, **kwargs): + additional_request = Request("https://example.org/price") + deferred = self.crawler.engine.download(additional_request) + additional_response = await maybe_deferred_to_future(deferred) + yield { + "h1": response.css("h1").get(), + "price": additional_response.css("#price").get(), + } + +You can also send multiple requests in parallel: + +.. code-block:: python + + from scrapy import Spider, Request + from scrapy.utils.defer import maybe_deferred_to_future + from twisted.internet.defer import DeferredList + + + 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"), + ] + deferreds = [] + for r in additional_requests: + deferred = self.crawler.engine.download(r) + deferreds.append(deferred) + responses = await maybe_deferred_to_future(DeferredList(deferreds)) + yield { + "h1": response.css("h1::text").get(), + "price": responses[0][1].css(".price::text").get(), + "price2": responses[1][1].css(".color::text").get(), + } + + .. _sync-async-spider-middleware: Mixing synchronous and asynchronous spider middlewares