Events & state
Platform events, state persistence, reboot, and status messages.
The platform sends events to a running Actor over a WebSocket. These events report resource usage, tell the Actor to save its state, and warn it before a migration or an abort. This page covers subscribing to events, persisting state with Actor.use_state(), rebooting after a migration and setting the run's status message.
Subscribing to events
Use Actor.on() to subscribe and Actor.off() to unsubscribe. Both are synchronous and need an initialized SDK. Events are members of Crawlee's Event enum, which scrapely re-exports.
import asyncio
from scrapely import Actor, Event
async def on_aborting() -> None:
await asyncio.sleep(1)
await Actor.exit()
async def main() -> None:
async with Actor:
Actor.on(Event.ABORTING, on_aborting)
...A listener can take no arguments or one argument, the event data, and it can be sync or async. Actor.off(event) without a listener removes every listener for that event.
Event names
| Member | Value | When Actor.on listeners receive it |
|---|---|---|
Event.PERSIST_STATE | persistState | Every persist_state_interval_millis (60 s by default), when the platform sends it, on migrating, on Actor.reboot() and at shutdown |
Event.SYSTEM_INFO | systemInfo | When the platform sends memory and CPU samples. Off the platform, every second |
Event.MIGRATING | migrating | The run is about to move to another server |
Event.ABORTING | aborting | A user or the API requested a graceful abort. The platform sends persistState first |
Event.EXIT | exit | Defined for compatibility. Scrape.ly doesn't send it |
PERSIST_STATE data has an is_migrating flag. SYSTEM_INFO data is Crawlee's EventSystemInfoData. The platform's deprecated cpuInfo message and any unknown messages are ignored.
Crawlee defines more events, such as SESSION_RETIRED, BROWSER_LAUNCHED, BROWSER_RETIRED, BROWSER_CLOSED, PAGE_CREATED, PAGE_CLOSED and CRAWLER_STATUS. Crawlers emit these on Crawlee's own event manager.
No automatic shutdown handlers
The SDK doesn't exit on aborting or reboot on migrating by itself. InitOptions.graceful_shutdown is accepted but not used. If you want those behaviours, register the listeners, as the official templates do for aborting.
If the container gets SIGTERM or SIGINT, the SDK runs a bounded shutdown and exits with code 0. See Signals.
Connection
When ACTOR_EVENTS_WEBSOCKET_URL is set, ScrapelyEventManager connects to it with the run token in the background and reconnects with backoff. A failed connection never blocks Actor.init(). Without the URL, the SDK uses Crawlee's LocalEventManager.
State persistence
A run can move to another server, and anything held only in memory is lost. Keep state that must survive in a key-value store.
Actor.use_state()
state = await Actor.use_state(default_value={"processed": 0, "cursor": None})
state["processed"] += 1use_state(default_value=None, key=None, kvs_name=None) returns a mutable dict backed by a key-value store record. The default key is APIFY_GLOBAL_STATE, and the default store is the run's default key-value store. Change the dict in place. Rebinding the name (state = {...}) doesn't change what gets saved.
The dict is saved whenever Crawlee's global event manager emits persistState. That happens about every minute while a crawler runs, and once when the crawler finishes. If no crawler is running, or if you need a save at a specific moment, save the dict explicitly:
store = await Actor.open_key_value_store() # pass name=... if you used kvs_name
await store.persist_autosaved_values()To save state before a migration, forward the platform event:
async def on_migrating(event_data) -> None:
store = await Actor.open_key_value_store()
await store.persist_autosaved_values()
await Actor.reboot()
Actor.on(Event.MIGRATING, on_migrating)After the restart, use_state() loads the saved value, so the Actor resumes where it stopped.
state = await Actor.use_state(default_value={"cursor": 0}, key="crawl-state", kvs_name="state-store")Actor.reboot()
await Actor.reboot(options=None)reboot() restarts the run in a fresh container with the same run ID and the same default storages. It runs these steps in order:
- Emits
PERSIST_STATEwithis_migrating=TruetoActor.on()listeners. - Waits one second.
- Calls
POST /v2/actor-runs/{runId}/reboot. - Waits for the platform to stop the container. The wait is
RebootOptions.custom_after_sleep_millis, ormetamorph_after_sleep_millis(300000 ms by default). - If the container is still alive after the wait, flushes pending charges and calls
sys.exit(0).
Off the platform, reboot() logs a warning and returns. Without a run ID, it raises RuntimeError.
Status messages
from scrapely import Actor, SetStatusMessageOptions
await Actor.set_status_message("Downloading page 3")
await Actor.set_status_message("Completed", is_terminal=True)
await Actor.set_status_message(
"Retrying upstream",
SetStatusMessageOptions(level="WARNING"),
)set_status_message(message, options=None, *, is_terminal=None) first logs [Status message]: <message> at options.level (DEBUG, INFO, WARNING or ERROR). It then sends PUT /v2/actor-runs/{runId} with statusMessage and isStatusMessageTerminal, and returns the updated ActorRun. Without API credentials, it only logs, and it returns an empty ActorRun.
is_terminal overrides SetStatusMessageOptions.is_status_message_terminal. A terminal message is meant to be the run's final message.
Actor.status_message is a separate value. It holds the message passed to exit() or fail(). On the platform, exit() and fail() also save that message on the run as a terminal message.
What's next
- Storages covers where persisted state lives.
- Errors & deployment covers testing and troubleshooting.
- The API reference lists every signature.