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
23 changes: 0 additions & 23 deletions docs/examples/accept_user_input.mdx

This file was deleted.

4 changes: 0 additions & 4 deletions docs/examples/accept_user_input.ts

This file was deleted.

2 changes: 1 addition & 1 deletion docs/examples/crawl_single_url.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ to grab the HTML of a web page.
{CrawlSource}
</RunnableCodeBlock>

If you don't want to hard-code the URL into the script, refer to the [Accept User Input](./accept-user-input) example.
If you don't want to hard-code the URL into the script, read it from a command-line argument or an environment variable, or, on the Apify platform, from the Actor input with [`Actor.getInput()`](https://docs.apify.com/sdk/js/reference/class/Actor#getInput).
14 changes: 4 additions & 10 deletions docs/guides/result_storage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ By default, Crawlee storage is managed by the <ApiLink to="fs-storage/class/File

The key-value store is used for saving and reading data records or files. Each data record is represented by a unique key and associated with a MIME content type. Key-value stores are ideal for saving screenshots of web pages, PDFs or to persist the state of crawlers.

Each Crawlee project run is associated with a **default key-value store**. By convention, the project input and output are stored in the default key-value store under the `INPUT` and `OUTPUT` keys respectively. Typically, both input and output are JSON files, although they could be any other format.
Each Crawlee project run is associated with a **default key-value store**. It is purged when the run starts (see [`purgeDefaultStorages()`](#cleaning-up-the-storages)), so it is the place for data that belongs to a single run.

In Crawlee, the key-value store is represented by the <ApiLink to="core/class/KeyValueStore">`KeyValueStore`</ApiLink> class. In order to simplify access to the default key-value store, Crawlee also provides <ApiLink to="core/class/KeyValueStore#getValue">`KeyValueStore.getValue()`</ApiLink> and <ApiLink to="core/class/KeyValueStore#setValue">`KeyValueStore.setValue()`</ApiLink> functions.

Expand All @@ -29,7 +29,7 @@ The data is stored in the directory specified by the `CRAWLEE_STORAGE_DIR` envir

`{STORE_ID}` is the name or the ID of the key-value store. The default key-value store has ID `default`. The `{KEY}` is the key of the record, used as the filename verbatim — no extension is derived from the content type, which is recorded in the `{KEY}.__metadata__.json` sidecar instead.

A file you drop into the directory by hand is adopted as a record when the store is opened, keyed by its filename (see the [v4 upgrading guide](../upgrading/upgrading-to-v4#out-of-band-key-value-files-eg-a-hand-placed-inputjson) for the run-input exception). Files added while the crawler is running are not picked up — write those with `setValue()`.
A file you drop into the directory by hand is adopted as a record when the store is opened, keyed by its filename (see the [v4 upgrading guide](../upgrading/upgrading-to-v4#out-of-band-key-value-files-eg-a-hand-placed-inputjson)). Files added while the crawler is running are not picked up — write those with `setValue()`.

:::

Expand All @@ -38,12 +38,6 @@ The following code demonstrates basic operations of key-value stores:
```javascript
import { KeyValueStore } from 'crawlee';

// Get the INPUT from the default key-value store
const input = await KeyValueStore.getInput();

// Write the OUTPUT to the default key-value store
await KeyValueStore.setValue('OUTPUT', { myResult: 123 });

// Open a named key-value store
const store = await KeyValueStore.open('some-name');

Expand All @@ -61,7 +55,7 @@ const value = await store.getValue('some-key');
await store.setValue('some-key', null);
```

To see a real-world example of how to get the input from the key-value store, see the [Screenshots](../examples/capture-screenshot) example.
To see a real-world example of using the key-value store, see the [Screenshots](../examples/capture-screenshot) example.

## Dataset

Expand Down Expand Up @@ -113,7 +107,7 @@ import { purgeDefaultStorages } from 'crawlee';
await purgeDefaultStorages();
```

Calling this function will clean up the run-scoped results storage directories - the default ones and any alias-keyed one - except the `INPUT` key in the default key-value store directory. This is a shortcut for running (optional) `purge` method on the <ApiLink to="core/interface/StorageBackend">`StorageBackend`</ApiLink> interface, in other words it will call the `purge` method of the underlying storage implementation we are currently using. In addition, this method will make sure the storage is purged only once for a given execution context, so it is safe to call it multiple times.
Calling this function will clean up the run-scoped results storage directories - the default ones and any alias-keyed one. This is a shortcut for running (optional) `purge` method on the <ApiLink to="core/interface/StorageBackend">`StorageBackend`</ApiLink> interface, in other words it will call the `purge` method of the underlying storage implementation we are currently using. In addition, this method will make sure the storage is purged only once for a given execution context, so it is safe to call it multiple times.

## Transactional storage

Expand Down
1 change: 0 additions & 1 deletion docs/public-api/crawlee-core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ export const crawleeConfigFields: {
persistStateIntervalMillis: ConfigField<z.ZodDefault<z.ZodPreprocess<z.ZodNumber, unknown>>>;
internalTimeoutMillis: ConfigField<z.ZodOptional<z.ZodPreprocess<z.ZodNumber, unknown>>>;
systemInfoIntervalMillis: ConfigField<z.ZodDefault<z.ZodPreprocess<z.ZodNumber, unknown>>>;
inputKey: ConfigField<z.ZodDefault<z.ZodString>>;
headless: ConfigField<z.ZodDefault<z.ZodPreprocess<z.ZodBoolean, unknown>>>;
xvfb: ConfigField<z.ZodDefault<z.ZodPreprocess<z.ZodBoolean, unknown>>>;
chromeExecutablePath: ConfigField<z.ZodOptional<z.ZodString>>;
Expand Down
16 changes: 15 additions & 1 deletion docs/public-api/crawlee-fs-storage.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

```ts

import { AdoptionCandidate } from '@crawlee/fs-storage-native';
import type { CrawleeLogger } from '@crawlee/types';
import type * as storage from '@crawlee/types';

export { AdoptionCandidate }

// @public
export class FileSystemStorageBackend implements storage.StorageBackend {
constructor(options: FileSystemStorageOptions);
Expand All @@ -19,13 +22,15 @@ export class FileSystemStorageBackend implements storage.StorageBackend {
// (undocumented)
readonly datasetsDirectory: string;
getStorageBackendCacheKey(): string;
protected keyValueStoreAdoptionCandidates(_options: KeyValueStoreHookOptions): AdoptionCandidate[];
// (undocumented)
readonly keyValueStoresDirectory: string;
// (undocumented)
readonly localDataDirectory: string;
// (undocumented)
readonly logger?: CrawleeLogger;
purge(): Promise<void>;
protected purgeKeyValueStore(store: PurgeableKeyValueStoreBackend, _options: KeyValueStoreHookOptions): Promise<void>;
// (undocumented)
readonly requestQueueAccess: 'single' | 'shared';
// (undocumented)
Expand All @@ -37,12 +42,21 @@ export class FileSystemStorageBackend implements storage.StorageBackend {

// @public (undocumented)
export interface FileSystemStorageOptions {
inputKey?: string;
localDataDirectory: string;
logger?: CrawleeLogger;
requestQueueAccess?: 'single' | 'shared';
}

// @public
export interface KeyValueStoreHookOptions {
isDefaultStore: boolean;
}

// @public
export interface PurgeableKeyValueStoreBackend extends storage.KeyValueStoreBackend {
purgeExcept(keys: string[]): Promise<void>;
}

// (No @packageDocumentation comment for this package)

```
13 changes: 11 additions & 2 deletions docs/upgrading/upgrading_v4.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,14 @@ Configuration instances are immutable — attempting to assign a property throws
Previously, environment variables always won. Now `new Configuration({ headless: false })`
works even when `CRAWLEE_HEADLESS=true` is set.

### `KeyValueStore.getInput()` and `Configuration.inputKey` moved to the Apify SDK

Reading the run input is an Apify platform concern, so Crawlee no longer has any notion of it:

- **`KeyValueStore.getInput()` is removed.** Use `Actor.getInput()` from `apify`, which also handles the platform-assigned input key, encrypted secrets and input schema defaults.
- **`Configuration.inputKey` and the `CRAWLEE_INPUT_KEY` environment variable are removed.** The Apify SDK's `Configuration` defines `inputKey` itself.
- **The default key-value store is purged in full**, `INPUT` included. Sparing a key, and claiming a bare `<key>` / `<key>.json` file as the record `<key>` (see [Out-of-band key-value files](#out-of-band-key-value-files-eg-a-hand-placed-inputjson)), is left to subclasses of `FileSystemStorageBackend` through its two protected hooks, `keyValueStoreAdoptionCandidates` and `purgeKeyValueStore`. The Apify SDK's `ApifyFileSystemStorageBackend` does this for its run input; plain Crawlee treats a hand-placed `INPUT.json` as any other file.

### Service management moved from `Configuration` to `ServiceLocator`

The service management functionality has been extracted from `Configuration` into a new `ServiceLocator` class.
Expand Down Expand Up @@ -1879,10 +1887,10 @@ Keys are literal. `aaa` and `aaa.json` are two distinct keys, and `FileSystemSto

What v4 does instead is *adopt* value files that turn up in a store directory without the `<key>.__metadata__.json` sidecar that marks a record — the Apify CLI's input, a project template, a v3 store directory, a file you dropped in with an editor. Opening the store writes the missing sidecar (the value bytes are never touched), and from then on the file is an ordinary record: read by `getValue`, enumerated by `listKeys`, removed by `deleteValue`. Two rules decide the key:

- In the **default** store, the run-input keys (`INPUT` and the configured `inputKey`) claim a bare `INPUT` or `INPUT.json`. The key is `INPUT` while the file keeps its name, so `listKeys` reports `INPUT`, `getValue('INPUT.json')` is `undefined`, and `getPublicUrl('INPUT')` points at `INPUT.json`. If both files are present, opening the store fails instead of guessing which one is the input.
- A subclass of `FileSystemStorageBackend` may claim specific files under a key of its own by overriding `keyValueStoreAdoptionCandidates`. The Apify SDK's `ApifyFileSystemStorageBackend` claims a bare `INPUT` or `INPUT.json` in the **default** store (and the same for the configured `ACTOR_INPUT_KEY`) as the record `INPUT`: the file keeps its name, `listKeys` reports `INPUT`, `getValue('INPUT.json')` is `undefined`, and `getPublicUrl('INPUT')` points at `INPUT.json`. If both files are present, opening the store fails instead of guessing which one is the input. Plain Crawlee claims nothing, so there a bare `INPUT.json` is just a file named `INPUT.json`.
- Every other sidecar-less file becomes a record **keyed by its filename**, in every store: a hand-placed `some-key.json` is the key `some-key.json`, and so is an `INPUT.json` in a store other than the default one. Dotfiles are skipped.

A `.json` file is adopted as `application/json; charset=utf-8` and anything else as `application/octet-stream`; there is no content sniffing. Adopted records are subject to the purge of the default store on start like any other record — only the run-input keys are spared.
A `.json` file is adopted as `application/json; charset=utf-8` and anything else as `application/octet-stream`; there is no content sniffing. Adopted records are subject to the purge of the default store on start like any other record, unless a subclass spares them through `purgeKeyValueStore` — the Apify SDK keeps its run input this way.

Beyond the literal keys, three v3 behaviors are gone:

Expand Down Expand Up @@ -2239,6 +2247,7 @@ The full list of removed exports and members, for ctrl-F purposes. Where a repla
- `context.blockResources` and `context.cacheResponses` — no longer attached to the crawling context. The functionality is still available as deprecated functions, accessible both via the `puppeteerUtils` namespace (`puppeteerUtils.blockResources`, `puppeteerUtils.cacheResponses`) and as top-level exports from `@crawlee/puppeteer` (`import { blockResources, cacheResponses } from '@crawlee/puppeteer'`). Unlike the old context helpers, these take an explicit `page` argument — e.g. `await blockResources(page)`. Both are `@deprecated` and will be removed in a future release, so migrate away from them.
- `context.closeCookieModals`, `playwrightUtils.closeCookieModals` and `puppeteerUtils.closeCookieModals` — removed along with the optional `idcac-playwright` peer dependency (see [Crawling context no longer includes `closeCookieModals`](#crawling-context-no-longer-includes-closecookiemodals) and the [cookie modals guide](../guides/cookie-modals))
- `Configuration.systemInfoV2` / `CRAWLEE_SYSTEM_INFO_V2` environment variable — the v2 behavior is now the default (see [Available resource detection](#available-resource-detection))
- `KeyValueStore.getInput()` and `Configuration.inputKey` / `CRAWLEE_INPUT_KEY` — reading the run input moved to the Apify SDK (see [`KeyValueStore.getInput()` and `Configuration.inputKey` moved to the Apify SDK](#keyvaluestoregetinput-and-configurationinputkey-moved-to-the-apify-sdk))
- `Configuration.defaultDatasetId` / `defaultKeyValueStoreId` / `defaultRequestQueueId` and their `CRAWLEE_DEFAULT_*_ID` environment variables — the default storage is addressed by a reserved alias, not by a configurable ID. Open a storage by name if you need a specific one.
- `checkAndSerialize` and `chunkBySize` functions (from `@crawlee/core`) — value (de)serialization now lives in the `KeyValueStore` frontend; use `serializeValue` / `parseValue` (see [`maybeStringify` is removed](#maybestringify-is-removed))
- `BASIC_CRAWLER_TIMEOUT_BUFFER_SECS` constant (from `@crawlee/basic`) — was an internal timeout buffer, no longer exported
Expand Down
3 changes: 0 additions & 3 deletions packages/core/src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@ export const crawleeConfigFields = {
internalTimeoutMillis: field(coerceNumber.optional(), 'CRAWLEE_INTERNAL_TIMEOUT'),
/** @default 1_000 */
systemInfoIntervalMillis: field(coerceNumber.default(1_000)),
/** @default 'INPUT' */
inputKey: field(z.string().default('INPUT'), 'CRAWLEE_INPUT_KEY'),
/** @default true */
headless: field(coerceBoolean.default(true), 'CRAWLEE_HEADLESS'),
/** @default false */
Expand Down Expand Up @@ -160,7 +158,6 @@ export interface Configuration extends ResolvedConfigValues {}
*
* Key | Environment Variable | Default Value
* ---|---|---
* `inputKey` | `CRAWLEE_INPUT_KEY` | `'INPUT'`
* `xvfb` | `CRAWLEE_XVFB` | `false`
* `chromeExecutablePath` | `CRAWLEE_CHROME_EXECUTABLE_PATH` | -
* `defaultBrowserPath` | `CRAWLEE_DEFAULT_BROWSER_PATH` | -
Expand Down
8 changes: 1 addition & 7 deletions packages/core/src/memory-storage/memory-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,6 @@ export class MemoryStorageBackend implements storage.StorageBackend {
// marks them as run-scoped. `'default'` is the exception — it collapses onto the default storage.
const isRunScoped = (store: { name?: string }) => store.name === undefined || store.name === 'default';

const isDefault = (store: { name?: string; cacheKey: string }) =>
store.name === 'default' || store.cacheKey === 'default';

const purgeRunScoped = async <T extends { name?: string; cacheKey: string }>(
cache: T[],
purgeStore: (store: T) => Promise<void>,
Expand All @@ -203,10 +200,7 @@ export class MemoryStorageBackend implements storage.StorageBackend {
};

await Promise.all([
// Only the default store holds the run input, so it is the only one that keeps `INPUT`.
purgeRunScoped(this.#keyValueStoreBackendCache, async (store) =>
isDefault(store) ? store.purgeExceptInput() : store.purge(),
),
purgeRunScoped(this.#keyValueStoreBackendCache, async (store) => store.purge()),
purgeRunScoped(this.#datasetBackendCache, async (store) => store.purge()),
purgeRunScoped(this.#requestQueueBackendCache, async (store) => store.purge()),
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@ const inputRecordSchema = z.object({
contentType: z.string().min(1).optional(),
});

/**
* Key under which a run's input is stored in the default key-value store. Matches Crawlee's default
* `inputKey` (`CRAWLEE_INPUT_KEY`) and the `INPUT` files `FileSystemStorageBackend` preserves on purge.
*/
const KEY_VALUE_STORE_INPUT_KEY = 'INPUT';

export interface KeyValueStoreBackendOptions {
name?: string;
id?: string;
Expand Down Expand Up @@ -90,22 +84,6 @@ export class KeyValueStoreBackend extends BaseClient implements storage.KeyValue
this.updateTimestamps(true);
}

/**
* Purges every record except the run's input. Used by {@link MemoryStorageBackend.purge} for the
* default key-value store, mirroring `FileSystemStorageBackend`, which preserves `INPUT` (and its
* extension variants) when purging the default store. The in-memory key has no extension, so we
* preserve the bare `INPUT` key only.
*/
async purgeExceptInput(): Promise<void> {
for (const key of this.#keyValueEntries.keys()) {
if (key !== KEY_VALUE_STORE_INPUT_KEY) {
this.#keyValueEntries.delete(key);
}
}

this.updateTimestamps(true);
}

async listKeys(options: storage.KeyValueStoreListKeysOptions = {}): Promise<storage.KeyValueStoreListKeysResult> {
const { prefix, exclusiveStartKey, limit } = parseArgument(options, schemas.keyValueStoreListKeysOptions);

Expand Down
1 change: 0 additions & 1 deletion packages/core/src/service_locator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,6 @@ export class ServiceLocator implements ServiceLocatorInterface {
this.#storageBackend = configuration.persistStorage
? new FileSystemStorageBackend({
localDataDirectory: configuration.storageDir,
inputKey: configuration.inputKey,
logger: this.getLogger().child({ prefix: 'FileSystemStorageBackend' }),
})
: new MemoryStorageBackend({
Expand Down
Loading
Loading