Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ Each middleware is an async generator that wraps the next handler, enabling setu
Three-tier design:

- **High-level**: `Dataset`, `KeyValueStore`, `RequestQueue` in `src/crawlee/storages/`
- **Storage clients** (`src/crawlee/storage_clients/`): `FileSystemStorageClient` (default), `MemoryStorageClient`, `SqlStorageClient`, `RedisStorageClient`
- **Storage backends** (`src/crawlee/storage_backends/`): `FileSystemStorageBackend` (default), `MemoryStorageBackend`, `SqlStorageBackend`, `RedisStorageBackend`
- **Instance caching**: `StorageInstanceManager` is a global singleton that caches storage instances by ID/name

### Service locator

`src/crawlee/_service_locator.py` is a global singleton managing `Configuration`, `EventManager`, `StorageClient`, and `StorageInstanceManager`. Prevents double-initialization with `ServiceConflictError`.
`src/crawlee/_service_locator.py` is a global singleton managing `Configuration`, `EventManager`, `StorageBackend`, and `StorageInstanceManager`. Prevents double-initialization with `ServiceConflictError`.

### HTTP clients

Expand All @@ -65,6 +65,6 @@ Pluggable via `HttpClient` interface in `src/crawlee/http_clients/`:
- `crawlee._log_config`: the SDK calls `configure_logger(..., remove_old_handlers=True)` and uses `CrawleeLogFormatter` and `get_configured_log_level`. Keep their signatures and behavior stable, even though crawlee itself never passes `remove_old_handlers=True`.
- `crawlee._types`, `crawlee._request`, `crawlee._consts`, and helpers under `crawlee._utils` (`crypto`, `urls`, `models`, `file`, `requests`, `byte_size`, `log`, `try_import`).
- `crawlee.events._types` and `crawlee.events._event_manager`.
- `crawlee.storage_clients._base` and `crawlee.storage_clients._file_system`, whose file-system clients the SDK subclasses, so their internals count as an interface too.
- `crawlee.storage_backends._base` and `crawlee.storage_backends._file_system`, whose file-system backends the SDK subclasses, so their internals count as an interface too.

Before renaming, removing, or changing the behavior of anything in these modules, search apify-sdk-python for its use.
4 changes: 2 additions & 2 deletions docs/deployment/aws_lambda.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ For simple crawlers that don't require browser rendering, you can deploy using a

### Updating the code

When instantiating a crawler, use <ApiLink to="class/MemoryStorageClient">`MemoryStorageClient`</ApiLink>. By default, Crawlee uses file-based storage, but the Lambda filesystem is read-only (except for `/tmp`). Using `MemoryStorageClient` tells Crawlee to use in-memory storage instead.
When instantiating a crawler, use <ApiLink to="class/MemoryStorageBackend">`MemoryStorageBackend`</ApiLink>. By default, Crawlee uses file-based storage, but the Lambda filesystem is read-only (except for `/tmp`). Using `MemoryStorageBackend` tells Crawlee to use in-memory storage instead.

Wrap the crawler logic in a `lambda_handler` function. This is the entry point that AWS will execute.

Expand Down Expand Up @@ -113,7 +113,7 @@ For crawlers that require browser rendering, you need to deploy using Docker con

### Updating the code

As with <ApiLink to="class/BeautifulSoupCrawler">`BeautifulSoupCrawler`</ApiLink>, use <ApiLink to="class/MemoryStorageClient">`MemoryStorageClient`</ApiLink> and wrap the logic in a `lambda_handler` function. Additionally, configure `browser_launch_options` with flags optimized for serverless environments. These flags disable sandboxing and GPU features that aren't available in Lambda's containerized runtime.
As with <ApiLink to="class/BeautifulSoupCrawler">`BeautifulSoupCrawler`</ApiLink>, use <ApiLink to="class/MemoryStorageBackend">`MemoryStorageBackend`</ApiLink> and wrap the logic in a `lambda_handler` function. Additionally, configure `browser_launch_options` with flags optimized for serverless environments. These flags disable sandboxing and GPU features that aren't available in Lambda's containerized runtime.

<CodeBlock language="python" title="main.py">
{PlaywrightCrawlerLambda}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@
from aws_lambda_powertools.utilities.typing import LambdaContext

from crawlee.crawlers import BeautifulSoupCrawler, BeautifulSoupCrawlingContext
from crawlee.storage_clients import MemoryStorageClient
from crawlee.storage_backends import MemoryStorageBackend
from crawlee.storages import Dataset, RequestQueue


async def main() -> str:
# highlight-start
# Disable writing storage data to the file system
storage_client = MemoryStorageClient()
storage_backend = MemoryStorageBackend()
# highlight-end

# Initialize storages
dataset = await Dataset.open(storage_client=storage_client)
request_queue = await RequestQueue.open(storage_client=storage_client)
dataset = await Dataset.open(storage_backend=storage_backend)
request_queue = await RequestQueue.open(storage_backend=storage_backend)

crawler = BeautifulSoupCrawler(
storage_client=storage_client,
storage_backend=storage_backend,
max_request_retries=1,
request_handler_timeout=timedelta(seconds=30),
max_requests_per_crawl=10,
Expand Down
10 changes: 5 additions & 5 deletions docs/deployment/code_examples/aws/playwright_crawler_lambda.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@
from aws_lambda_powertools.utilities.typing import LambdaContext

from crawlee.crawlers import PlaywrightCrawler, PlaywrightCrawlingContext
from crawlee.storage_clients import MemoryStorageClient
from crawlee.storage_backends import MemoryStorageBackend
from crawlee.storages import Dataset, RequestQueue


async def main() -> str:
# highlight-start
# Disable writing storage data to the file system
storage_client = MemoryStorageClient()
storage_backend = MemoryStorageBackend()
# highlight-end

# Initialize storages
dataset = await Dataset.open(storage_client=storage_client)
request_queue = await RequestQueue.open(storage_client=storage_client)
dataset = await Dataset.open(storage_backend=storage_backend)
request_queue = await RequestQueue.open(storage_backend=storage_backend)

crawler = PlaywrightCrawler(
storage_client=storage_client,
storage_backend=storage_backend,
max_request_retries=1,
request_handler_timeout=timedelta(seconds=30),
max_requests_per_crawl=10,
Expand Down
6 changes: 3 additions & 3 deletions docs/deployment/code_examples/google/cloud_run_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@
from litestar import Litestar, get

from crawlee.crawlers import PlaywrightCrawler, PlaywrightCrawlingContext
from crawlee.storage_clients import MemoryStorageClient
from crawlee.storage_backends import MemoryStorageBackend


@get('/')
async def main() -> str:
"""The crawler entry point that will be called when the HTTP endpoint is accessed."""
# highlight-start
# Disable writing storage data to the file system
storage_client = MemoryStorageClient()
storage_backend = MemoryStorageBackend()
# highlight-end

crawler = PlaywrightCrawler(
headless=True,
max_requests_per_crawl=10,
browser_type='firefox',
storage_client=storage_client,
storage_backend=storage_backend,
)

@crawler.router.default_handler
Expand Down
6 changes: 3 additions & 3 deletions docs/deployment/code_examples/google/google_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@
from flask import Request, Response

from crawlee.crawlers import BeautifulSoupCrawler, BeautifulSoupCrawlingContext
from crawlee.storage_clients import MemoryStorageClient
from crawlee.storage_backends import MemoryStorageBackend


async def main() -> str:
# highlight-start
# Disable writing storage data to the file system
storage_client = MemoryStorageClient()
storage_backend = MemoryStorageBackend()
# highlight-end

crawler = BeautifulSoupCrawler(
storage_client=storage_client,
storage_backend=storage_backend,
max_request_retries=1,
request_handler_timeout=timedelta(seconds=30),
max_requests_per_crawl=10,
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/run_parallel_crawlers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This example demonstrates how to run two parallel crawlers where one crawler pro

In some situations, you may need different approaches for scraping data from a website. For example, you might use <ApiLink to="class/PlaywrightCrawler">`PlaywrightCrawler`</ApiLink> for navigating JavaScript-heavy pages and a faster, more lightweight <ApiLink to="class/ParselCrawler">`ParselCrawler`</ApiLink> for processing static pages. One way to solve this is to use <ApiLink to="class/AdaptivePlaywrightCrawler">`AdaptivePlaywrightCrawler`</ApiLink>, see the [Adaptive Playwright crawler example](./adaptive-playwright-crawler) to learn more.

The code below demonstrates an alternative approach using two separate crawlers. Links are passed between crawlers via <ApiLink to="class/RequestQueue">`RequestQueue`</ApiLink> aliases. The `keep_alive` option allows the Playwright crawler to run in the background and wait for incoming links without stopping when its queue is empty. You can also use different storage clients for each crawler without losing the ability to pass links between queues. Learn more about available storage clients in this [guide](/python/docs/guides/storage-clients).
The code below demonstrates an alternative approach using two separate crawlers. Links are passed between crawlers via <ApiLink to="class/RequestQueue">`RequestQueue`</ApiLink> aliases. The `keep_alive` option allows the Playwright crawler to run in the background and wait for incoming links without stopping when its queue is empty. You can also use different storage backends for each crawler without losing the ability to pass links between queues. Learn more about available storage backends in this [guide](../guides/storage-backends).

<RunnableCodeBlock className="language-python" language="python">
{RunParallelCrawlersExample}
Expand Down
30 changes: 15 additions & 15 deletions docs/guides/architecture_overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ They have a similar inheritance structure as the crawlers, with the base class b
Storages are the components that manage data in Crawlee. They provide a way to store and retrieve data during the crawling process. Crawlee's storage system consists of two main layers:

- **Storages**: High-level interfaces for interacting with different storage types
- **Storage clients**: Backend implementations that handle the actual data persistence and management (you will learn more about them in the next section)
- **Storage backends**: Implementations that handle the actual data persistence and management (you will learn more about them in the next section)

Crawlee provides three built-in storage types for managing data:

Expand Down Expand Up @@ -246,14 +246,14 @@ Storage --|> KeyValueStore
Storage --|> RequestQueue
```

## Storage clients
## Storage backends

Storage clients are the backend implementations for storages that handle interactions with different storage systems. They provide a unified interface for <ApiLink to="class/Dataset">`Dataset`</ApiLink>, <ApiLink to="class/KeyValueStore">`KeyValueStore`</ApiLink>, and <ApiLink to="class/RequestQueue">`RequestQueue`</ApiLink>, regardless of the underlying storage implementation.
Storage backends are the implementations behind storages that handle interactions with different storage systems. They provide a unified interface for <ApiLink to="class/Dataset">`Dataset`</ApiLink>, <ApiLink to="class/KeyValueStore">`KeyValueStore`</ApiLink>, and <ApiLink to="class/RequestQueue">`RequestQueue`</ApiLink>, regardless of the underlying storage implementation.

Crawlee provides several built-in storage client implementations:
Crawlee provides several built-in storage backend implementations:

- <ApiLink to="class/MemoryStorageClient">`MemoryStorageClient`</ApiLink> - Stores data in memory with no persistence (ideal for testing and fast operations).
- <ApiLink to="class/FileSystemStorageClient">`FileSystemStorageClient`</ApiLink> - Provides persistent file system storage with caching (default client).
- <ApiLink to="class/MemoryStorageBackend">`MemoryStorageBackend`</ApiLink> - Stores data in memory with no persistence (ideal for testing and fast operations).
- <ApiLink to="class/FileSystemStorageBackend">`FileSystemStorageBackend`</ApiLink> - Provides persistent file system storage with caching (default backend).
- [`ApifyStorageClient`](https://docs.apify.com/sdk/python/reference/class/ApifyStorageClient) - Manages storage on the [Apify platform](https://apify.com/) (cloud-based). It is implemented in the [Apify SDK](https://github.com/apify/apify-sdk-python). You can find more information about it in the [Apify SDK documentation](https://docs.apify.com/sdk/python/).

```mermaid
Expand All @@ -269,32 +269,32 @@ classDiagram
%% Abstract classes
%% ========================

class StorageClient {
class StorageBackend {
<<abstract>>
}

%% ========================
%% Specific classes
%% ========================

class MemoryStorageClient
class MemoryStorageBackend

class FileSystemStorageClient
class FileSystemStorageBackend

class ApifyStorageClient

%% ========================
%% Inheritance arrows
%% ========================

StorageClient --|> MemoryStorageClient
StorageClient --|> FileSystemStorageClient
StorageClient --|> ApifyStorageClient
StorageBackend --|> MemoryStorageBackend
StorageBackend --|> FileSystemStorageBackend
StorageBackend --|> ApifyStorageClient
```

Storage clients can be registered globally with the <ApiLink to="class/ServiceLocator">`ServiceLocator`</ApiLink> (you will learn more about the <ApiLink to="class/ServiceLocator">`ServiceLocator`</ApiLink> in the next section), passed directly to crawlers, or specified when opening individual storage instances. You can also create custom storage clients by implementing the <ApiLink to="class/StorageClient">`StorageClient`</ApiLink> interface.
Storage backends can be registered globally with the <ApiLink to="class/ServiceLocator">`ServiceLocator`</ApiLink> (you will learn more about the <ApiLink to="class/ServiceLocator">`ServiceLocator`</ApiLink> in the next section), passed directly to crawlers, or specified when opening individual storage instances. You can also create custom storage backends by implementing the <ApiLink to="class/StorageBackend">`StorageBackend`</ApiLink> interface.

See the [Storage clients guide](./storage-clients) for more details.
See the [Storage backends guide](./storage-backends) for more details.

## Request router

Expand All @@ -319,7 +319,7 @@ See the [Request router guide](./request-router) for detailed information and ex
The <ApiLink to="class/ServiceLocator">`ServiceLocator`</ApiLink> is a central registry for global services in Crawlee. It manages and provides access to core services throughout the framework, ensuring consistent configuration across all components. The service locator coordinates these three services:

- <ApiLink to="class/Configuration">`Configuration`</ApiLink> - Application-wide settings and parameters that control various aspects of Crawlee behavior.
- <ApiLink to="class/StorageClient">`StorageClient`</ApiLink> - Backend implementation for data storage across datasets, key-value stores, and request queues.
- <ApiLink to="class/StorageBackend">`StorageBackend`</ApiLink> - Backend implementation for data storage across datasets, key-value stores, and request queues.
- <ApiLink to="class/EventManager">`EventManager`</ApiLink> - Event coordination system for internal framework events and custom user hooks.

Services can be registered globally through the `service_locator` singleton instance, passed to crawler constructors, or provided when opening individual storage instances. The service locator includes conflict prevention mechanisms to ensure configuration consistency and prevent accidental service conflicts during runtime.
Expand Down
20 changes: 10 additions & 10 deletions docs/guides/code_examples/service_locator/service_conflicts.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import asyncio

from crawlee import service_locator
from crawlee.storage_clients import FileSystemStorageClient, MemoryStorageClient
from crawlee.storage_backends import FileSystemStorageBackend, MemoryStorageBackend


async def main() -> None:
# Register the storage client via service locator.
memory_storage_client = MemoryStorageClient()
service_locator.set_storage_client(memory_storage_client)
# Register the storage backend via service locator.
memory_storage_backend = MemoryStorageBackend()
service_locator.set_storage_backend(memory_storage_backend)

# Retrieve the storage client.
current_storage_client = service_locator.get_storage_client()
# Retrieve the storage backend.
current_storage_backend = service_locator.get_storage_backend()

# Try to set a different storage client, which will raise ServiceConflictError
# if storage client was already retrieved.
file_system_storage_client = FileSystemStorageClient()
service_locator.set_storage_client(file_system_storage_client)
# Try to set a different storage backend, which will raise ServiceConflictError
# if storage backend was already retrieved.
file_system_storage_backend = FileSystemStorageBackend()
service_locator.set_storage_backend(file_system_storage_backend)


if __name__ == '__main__':
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import asyncio

from crawlee.crawlers import ParselCrawler
from crawlee.storage_backends import MemoryStorageBackend


async def main() -> None:
storage_backend = MemoryStorageBackend()

# Register storage backend via crawler.
crawler = ParselCrawler(
storage_backend=storage_backend,
)


if __name__ == '__main__':
asyncio.run(main())

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import asyncio

from crawlee import service_locator
from crawlee.storage_backends import MemoryStorageBackend


async def main() -> None:
storage_backend = MemoryStorageBackend()

# Register storage backend via service locator.
service_locator.set_storage_backend(storage_backend)


if __name__ == '__main__':
asyncio.run(main())

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from crawlee import service_locator
from crawlee.configuration import Configuration
from crawlee.storage_clients import MemoryStorageClient
from crawlee.storage_backends import MemoryStorageBackend
from crawlee.storages import Dataset


Expand All @@ -22,7 +22,7 @@ async def main() -> None:
# Or set explicitly specific configuration if
# you do not want to rely on global defaults.
dataset_2 = await Dataset.open(
storage_client=MemoryStorageClient(), configuration=configuration
storage_backend=MemoryStorageBackend(), configuration=configuration
)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import asyncio

from crawlee.storage_backends import MemoryStorageBackend
from crawlee.storages import Dataset


async def main() -> None:
storage_backend = MemoryStorageBackend()

# Pass the storage backend to the dataset (or other storage) when opening it.
dataset = await Dataset.open(
storage_backend=storage_backend,
)


if __name__ == '__main__':
asyncio.run(main())

This file was deleted.

Loading
Loading