How to Use Streaming Mode for Real-Time Crawling Stats and Item Processing in Scrapling
Scrapling’s Spider.stream() method provides an async generator that yields scraped items immediately upon extraction while exposing live CrawlStats for real-time monitoring, implemented via a bounded in-memory channel in CrawlerEngine.
Scrapling is an open-source asynchronous web crawling framework designed for high-performance data extraction. When you need to process scraped items as they arrive rather than waiting for an entire crawl to complete, Scrapling’s streaming mode offers a robust solution for real-time crawling stats and item processing without buffering everything in memory.
Understanding Scrapling’s Streaming Architecture
Scrapling’s streaming capability relies on a tight integration between the Spider class and the CrawlerEngine, using async generators and memory object streams to decouple production from consumption.
Core Components
| Component | Role | Key Implementation |
|---|---|---|
Spider.stream |
Public async generator that yields items one-by-one and gives access to spider.stats during iteration. |
scrapling/spiders/spider.py – lines 90-103 |
CrawlerEngine.__aiter__ / _stream |
The engine drives the crawl, pushes each processed result into an internal memory object stream, and yields it to the spider. | scrapling/spiders/engine.py – lines 13-34 |
create_memory_object_stream |
Provides a bounded async queue (size 100) that decouples crawling tasks from the consumer, enabling back-pressure-aware streaming. | Imported from anyio in engine.py |
Spider.stats |
Proxy property that forwards to engine.stats while a stream is active, allowing real-time inspection of the CrawlStats dataclass. |
scrapling/spiders/spider.py – lines 11-16 |
CrawlStats |
Holds counters (requests, items scraped, bytes, response codes, etc.) that are updated continuously during the crawl. | scrapling/spiders/result.py – lines 41-67 |
Data Flow
spider.stream()creates a logger token, instantiatesCrawlerEngine, and entersasync for item in self._engine.- The engine spawns a task group;
run()performs the full crawl (await self.crawl()). - Inside
_stream,create_memory_object_streamreturns a sender (send) and receiver (recv) pair. Every processed result is pushed viaawait self._item_stream.send(processed_result)(seeengine.pylines 144-146). - The spider’s
async forloop consumes items fromrecv, yielding them to the caller in real time. - While the loop runs,
spider.statsreads the liveengine.statsobject, which is continuously updated by methods such asincrement_requests_count,increment_status, etc., defined inCrawlStats.
Because the stream uses an in-memory bounded channel, the crawl will pause when the consumer falls behind, preventing unbounded memory growth.
Accessing Real-Time Crawling Statistics
The CrawlStats dataclass tracks comprehensive metrics that update continuously during streaming:
requests_count,failed_requests_count,blocked_requests_countitems_scraped,items_droppedresponse_status_count(HTTP-code histogram)response_bytesand per-domain byte countersdownload_delay,concurrent_requests, andconcurrent_requests_per_domain- Custom metrics (
custom_stats) and logger level counters (log_levels_counter)
All fields are updated during crawling; the spider.stats property makes them instantly readable inside the streaming loop.
Practical Implementation Examples
Basic Streaming Loop
import asyncio
from scrapling.spiders.spider import Spider
class MySpider(Spider):
start_urls = ["https://example.com"]
async def parse(self, response):
# Yield a dict for each page
yield {"url": response.url, "title": response.title}
# Optionally generate follow-up requests
if "next" in response.links:
yield Request(response.links["next"], sid="default")
async def main():
spider = MySpider()
async for item in spider.stream():
print("Item:", item)
# Live stats are always available
print("Requests so far:", spider.stats.requests_count)
asyncio.run(main())
Key points:
spider.stream()must be called from an async context.- Inside the loop,
spider.statsreflects the latest crawl status.
Live Dashboard Integration
import asyncio
import websockets
from scrapling.spiders.spider import Spider
class DashboardSpider(Spider):
start_urls = ["https://news.ycombinator.com"]
async def parse(self, response):
yield {"title": response.title, "url": response.url}
async def feed_websocket():
async with websockets.connect("ws://localhost:8765") as ws:
spider = DashboardSpider()
async for item in spider.stream():
await ws.send(json.dumps({"item": item, "stats": spider.stats.__dict__}))
asyncio.run(feed_websocket())
Each item is pushed immediately to a WebSocket client, together with a snapshot of the current CrawlStats.
Processing Follow-Up Requests in Stream Mode
async def parse(response):
# Yield a new request before emitting the current item
if "details" in response.links:
yield Request(response.links["details"], sid="default")
yield {"url": response.url, "summary": response.text[:200]}
spider = MySpider()
spider.parse = parse # Swap parser at runtime
async for item in spider.stream():
print(item) # Will include items from the follow-up request as they arrive
The test suite (tests/spiders/test_engine.py) validates exactly this behavior (see lines 22-30 for the follow-up request test).
Key Source Files and Implementation Details
| File | Purpose | Link |
|---|---|---|
scrapling/spiders/spider.py |
Public spider API, stream() generator, stats proxy |
spider.py |
scrapling/spiders/engine.py |
Core crawling engine, async iterator, memory stream handling | engine.py |
scrapling/spiders/result.py |
CrawlStats dataclass and CrawlResult wrapper |
result.py |
tests/spiders/test_engine.py |
Test suite covering streaming, stats, and pause logic | test_engine.py |
scrapling/cli.py |
CLI entry point – the --stream flag can trigger streaming mode from the command line |
cli.py |
These files together implement the streaming capability, provide real-time statistics, and expose a clean async API for developers.
Summary
- Streaming mode in Scrapling is enabled via
spider.stream(), an async generator that yields items as they are scraped rather than batching results at the end. - The architecture uses
create_memory_object_streamfrom anyio to create a bounded queue (size 100) that decouples the crawler from the consumer, providing automatic back-pressure handling. - Real-time statistics are available through the
spider.statsproperty, which proxies toengine.statsand exposes theCrawlStatsdataclass with counters for requests, items, bytes, and HTTP status codes. - The implementation spans
scrapling/spiders/spider.pyfor the public API,scrapling/spiders/engine.pyfor the async iteration logic, andscrapling/spiders/result.pyfor the statistics dataclass.
Frequently Asked Questions
How does Scrapling handle backpressure in streaming mode?
Scrapling uses a bounded memory object stream with a default buffer size of 100 items, implemented via create_memory_object_stream from the anyio library in scrapling/spiders/engine.py. When the consumer falls behind, the crawler automatically pauses until the buffer drains, preventing unbounded memory growth during long-running crawls.
Can I access crawl statistics outside of the streaming loop?
The spider.stats property is specifically designed to be accessed during active streaming, as it proxies to the live engine.stats object maintained by CrawlerEngine. While the stats object exists after crawling completes, the real-time updates only occur during the async for iteration over spider.stream(), making the streaming loop the intended context for monitoring CrawlStats.
What types of objects can be yielded when using streaming mode?
Within your parse method, you can yield dictionaries representing scraped items or Request objects to schedule follow-up crawls. The CrawlerEngine in scrapling/spiders/engine.py processes both types through its _stream method, pushing them into the memory object stream where they are yielded to your consumer loop in real-time, as validated in tests/spiders/test_engine.py lines 22-30.
How do I enable streaming mode from the command line?
Scrapling’s CLI interface in scrapling/cli.py supports a --stream flag that triggers streaming mode when invoking spiders from the terminal. When this flag is active, the CLI consumes the async generator returned by spider.stream() and outputs items as they are scraped, rather than collecting them into a final batch for post-crawl processing.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →